diff --git a/MareSynchronos/FileCache/TransientResourceManager.cs b/MareSynchronos/FileCache/TransientResourceManager.cs index 3d1b433..577e6fd 100644 --- a/MareSynchronos/FileCache/TransientResourceManager.cs +++ b/MareSynchronos/FileCache/TransientResourceManager.cs @@ -246,7 +246,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase private void DalamudUtil_FrameworkUpdate() { - _cachedFrameAddresses = new(_playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.CurrentAddress(), c => c.ObjectKind)); + _cachedFrameAddresses = new(_playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.Address, c => c.ObjectKind)); lock (_cacheAdditionLock) { _cachedHandledPaths.Clear(); @@ -371,7 +371,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase if (isAdded) { Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath); - SendTransients(gameObjectAddress); + SendTransients(gameObjectAddress, objectKind); } } } @@ -382,7 +382,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase } } - private void SendTransients(nint gameObject) + private void SendTransients(nint gameObject, ObjectKind objectKind) { _ = Task.Run(async () => { @@ -391,7 +391,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase _sendTransientCts = new(); var token = _sendTransientCts.Token; await Task.Delay(TimeSpan.FromSeconds(5), token).ConfigureAwait(false); - Mediator.Publish(new TransientResourceChangedMessage(gameObject)); + foreach (var kvp in TransientResources) + { + if (TransientResources.TryGetValue(objectKind, out var values) && values.Any()) + { + Logger.LogTrace("Sending Transients for {kind}", objectKind); + Mediator.Publish(new TransientResourceChangedMessage(gameObject)); + } + } }); } diff --git a/MareSynchronos/PlayerData/Data/CharacterData.cs b/MareSynchronos/PlayerData/Data/CharacterData.cs index f55bab8..92cf07e 100644 --- a/MareSynchronos/PlayerData/Data/CharacterData.cs +++ b/MareSynchronos/PlayerData/Data/CharacterData.cs @@ -15,6 +15,32 @@ public class CharacterData public string PetNamesData { get; set; } = string.Empty; public string MoodlesData { get; set; } = string.Empty; + public void SetFragment(ObjectKind kind, CharacterDataFragment? fragment) + { + if (kind == ObjectKind.Player) + { + var playerFragment = (fragment as CharacterDataFragmentPlayer); + HeelsData = playerFragment?.HeelsData ?? string.Empty; + HonorificData = playerFragment?.HonorificData ?? string.Empty; + ManipulationString = playerFragment?.ManipulationString ?? string.Empty; + MoodlesData = playerFragment?.MoodlesData ?? string.Empty; + PetNamesData = playerFragment?.PetNamesData ?? string.Empty; + } + + if (fragment is null) + { + CustomizePlusScale.Remove(kind); + FileReplacements.Remove(kind); + GlamourerString.Remove(kind); + } + else + { + CustomizePlusScale[kind] = fragment.CustomizePlusScale; + FileReplacements[kind] = fragment.FileReplacements; + GlamourerString[kind] = fragment.GlamourerString; + } + } + public API.Data.CharacterData ToAPI() { Dictionary> fileReplacements = diff --git a/MareSynchronos/PlayerData/Data/CharacterDataFragment.cs b/MareSynchronos/PlayerData/Data/CharacterDataFragment.cs new file mode 100644 index 0000000..7e60d7e --- /dev/null +++ b/MareSynchronos/PlayerData/Data/CharacterDataFragment.cs @@ -0,0 +1,8 @@ +namespace MareSynchronos.PlayerData.Data; + +public class CharacterDataFragment +{ + public string CustomizePlusScale { get; set; } = string.Empty; + public HashSet FileReplacements { get; set; } = []; + public string GlamourerString { get; set; } = string.Empty; +} diff --git a/MareSynchronos/PlayerData/Data/CharacterDataFragmentPlayer.cs b/MareSynchronos/PlayerData/Data/CharacterDataFragmentPlayer.cs new file mode 100644 index 0000000..318173c --- /dev/null +++ b/MareSynchronos/PlayerData/Data/CharacterDataFragmentPlayer.cs @@ -0,0 +1,10 @@ +namespace MareSynchronos.PlayerData.Data; + +public class CharacterDataFragmentPlayer : CharacterDataFragment +{ + public string HeelsData { get; set; } = string.Empty; + public string HonorificData { get; set; } = string.Empty; + public string ManipulationString { get; set; } = string.Empty; + public string MoodlesData { get; set; } = string.Empty; + public string PetNamesData { get; set; } = string.Empty; +} diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 13a0680..398ba6e 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -38,14 +38,14 @@ public class PlayerDataFactory _logger.LogTrace("Creating {this}", nameof(PlayerDataFactory)); } - public async Task BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token) + public async Task BuildCharacterData(GameObjectHandler playerRelatedObject, CancellationToken token) { if (!_ipcManager.Initialized) { throw new InvalidOperationException("Penumbra or Glamourer is not connected"); } - if (playerRelatedObject == null) return; + if (playerRelatedObject == null) return null; bool pointerIsZero = true; try @@ -69,23 +69,15 @@ public class PlayerDataFactory if (pointerIsZero) { _logger.LogTrace("Pointer was zero for {objectKind}", playerRelatedObject.ObjectKind); - previousData.FileReplacements.Remove(playerRelatedObject.ObjectKind); - previousData.GlamourerString.Remove(playerRelatedObject.ObjectKind); - previousData.CustomizePlusScale.Remove(playerRelatedObject.ObjectKind); - return; + return null; } - var previousFileReplacements = previousData.FileReplacements.ToDictionary(d => d.Key, d => d.Value); - var previousGlamourerData = previousData.GlamourerString.ToDictionary(d => d.Key, d => d.Value); - var previousCustomize = previousData.CustomizePlusScale.ToDictionary(d => d.Key, d => d.Value); - try { - await _performanceCollector.LogPerformance(this, $"CreateCharacterData>{playerRelatedObject.ObjectKind}", async () => + return await _performanceCollector.LogPerformance(this, $"CreateCharacterData>{playerRelatedObject.ObjectKind}", async () => { - await CreateCharacterData(previousData, playerRelatedObject, token).ConfigureAwait(false); + return await CreateCharacterData(playerRelatedObject, token).ConfigureAwait(false); }).ConfigureAwait(true); - return; } catch (OperationCanceledException) { @@ -97,9 +89,7 @@ public class PlayerDataFactory _logger.LogWarning(e, "Failed to create {object} data", playerRelatedObject); } - previousData.FileReplacements = previousFileReplacements; - previousData.GlamourerString = previousGlamourerData; - previousData.CustomizePlusScale = previousCustomize; + return null; } private async Task CheckForNullDrawObject(IntPtr playerPointer) @@ -112,32 +102,25 @@ public class PlayerDataFactory return ((Character*)playerPointer)->GameObject.DrawObject == null; } - private async Task CreateCharacterData(CharacterData data, GameObjectHandler playerRelatedObject, CancellationToken token) + private async Task CreateCharacterData(GameObjectHandler playerRelatedObject, CancellationToken ct) { var objectKind = playerRelatedObject.ObjectKind; + CharacterDataFragment fragment = objectKind == ObjectKind.Player ? new CharacterDataFragmentPlayer() : new(); _logger.LogDebug("Building character data for {obj}", playerRelatedObject); - if (!data.FileReplacements.TryGetValue(objectKind, out HashSet? value)) - { - data.FileReplacements[objectKind] = new(FileReplacementComparer.Instance); - } - else - { - value.Clear(); - } - - data.CustomizePlusScale.Remove(objectKind); - // wait until chara is not drawing and present so nothing spontaneously explodes - await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: token).ConfigureAwait(false); + await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct).ConfigureAwait(false); int totalWaitTime = 10000; while (!await _dalamudUtil.IsObjectPresentAsync(await _dalamudUtil.CreateGameObjectAsync(playerRelatedObject.Address).ConfigureAwait(false)).ConfigureAwait(false) && totalWaitTime > 0) { _logger.LogTrace("Character is null but it shouldn't be, waiting"); - await Task.Delay(50, token).ConfigureAwait(false); + await Task.Delay(50, ct).ConfigureAwait(false); totalWaitTime -= 50; } + + ct.ThrowIfCancellationRequested(); + Dictionary>? boneIndices = objectKind != ObjectKind.Player ? null @@ -151,24 +134,29 @@ public class PlayerDataFactory resolvedPaths = await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false); if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data"); - data.FileReplacements[objectKind] = + ct.ThrowIfCancellationRequested(); + + fragment.FileReplacements = new HashSet(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance) .Where(p => p.HasFileReplacement).ToHashSet(); - data.FileReplacements[objectKind].RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); + fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); + + ct.ThrowIfCancellationRequested(); _logger.LogDebug("== Static Replacements =="); - foreach (var replacement in data.FileReplacements[objectKind].Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) + foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) { _logger.LogDebug("=> {repl}", replacement); + ct.ThrowIfCancellationRequested(); } - await _transientResourceManager.WaitForRecording(token).ConfigureAwait(false); + await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false); // if it's pet then it's summoner, if it's summoner we actually want to keep all filereplacements alive at all times // or we get into redraw city for every change and nothing works properly if (objectKind == ObjectKind.Pet) { - foreach (var item in data.FileReplacements[ObjectKind.Pet].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths)) + foreach (var item in fragment.FileReplacements.Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths)) { if (_transientResourceManager.AddTransientResource(objectKind, item)) { @@ -176,14 +164,16 @@ public class PlayerDataFactory } } - _logger.LogTrace("Clearing {count} Static Replacements for Pet", data.FileReplacements[ObjectKind.Pet].Count); - data.FileReplacements[ObjectKind.Pet].Clear(); + _logger.LogTrace("Clearing {count} Static Replacements for Pet", fragment.FileReplacements.Count); + fragment.FileReplacements.Clear(); } + ct.ThrowIfCancellationRequested(); + _logger.LogDebug("Handling transient update for {obj}", playerRelatedObject); // remove all potentially gathered paths from the transient resource manager that are resolved through static resolving - _transientResourceManager.ClearTransientPaths(objectKind, data.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList()); + _transientResourceManager.ClearTransientPaths(objectKind, fragment.FileReplacements.SelectMany(c => c.GamePaths).ToList()); // get all remaining paths and resolve them var transientPaths = ManageSemiTransientData(objectKind); @@ -193,63 +183,67 @@ public class PlayerDataFactory foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal)) { _logger.LogDebug("=> {repl}", replacement); - data.FileReplacements[objectKind].Add(replacement); + fragment.FileReplacements.Add(replacement); } // clean up all semi transient resources that don't have any file replacement (aka null resolve) - _transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. data.FileReplacements[objectKind]]); + _transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. fragment.FileReplacements]); + + ct.ThrowIfCancellationRequested(); // make sure we only return data that actually has file replacements - foreach (var item in data.FileReplacements) - { - data.FileReplacements[item.Key] = new HashSet(item.Value.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance); - } + fragment.FileReplacements = new HashSet(fragment.FileReplacements.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance); // gather up data from ipc - data.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations(); Task getHeelsOffset = _ipcManager.Heels.GetOffsetAsync(); Task getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address); Task getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address); Task getHonorificTitle = _ipcManager.Honorific.GetTitle(); - data.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false); - _logger.LogDebug("Glamourer is now: {data}", data.GlamourerString[playerRelatedObject.ObjectKind]); + fragment.GlamourerString = await getGlamourerData.ConfigureAwait(false); + _logger.LogDebug("Glamourer is now: {data}", fragment.GlamourerString); var customizeScale = await getCustomizeData.ConfigureAwait(false); - data.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty; - _logger.LogDebug("Customize is now: {data}", data.CustomizePlusScale[playerRelatedObject.ObjectKind]); - data.HonorificData = await getHonorificTitle.ConfigureAwait(false); - _logger.LogDebug("Honorific is now: {data}", data.HonorificData); - data.HeelsData = await getHeelsOffset.ConfigureAwait(false); - _logger.LogDebug("Heels is now: {heels}", data.HeelsData); + fragment.CustomizePlusScale = customizeScale ?? string.Empty; + _logger.LogDebug("Customize is now: {data}", fragment.CustomizePlusScale); + if (objectKind == ObjectKind.Player) { - data.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty; - _logger.LogDebug("Moodles is now: {moodles}", data.MoodlesData); + var playerFragment = (fragment as CharacterDataFragmentPlayer)!; + playerFragment.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations(); - data.PetNamesData = _ipcManager.PetNames.GetLocalNames(); - _logger.LogDebug("Pet Nicknames is now: {petnames}", data.PetNamesData); + playerFragment!.HonorificData = await getHonorificTitle.ConfigureAwait(false); + _logger.LogDebug("Honorific is now: {data}", playerFragment!.HonorificData); + + playerFragment!.HeelsData = await getHeelsOffset.ConfigureAwait(false); + _logger.LogDebug("Heels is now: {heels}", playerFragment!.HeelsData); + + playerFragment!.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty; + _logger.LogDebug("Moodles is now: {moodles}", playerFragment!.MoodlesData); + + playerFragment!.PetNamesData = _ipcManager.PetNames.GetLocalNames(); + _logger.LogDebug("Pet Nicknames is now: {petnames}", playerFragment!.PetNamesData); } - if (data.FileReplacements.TryGetValue(objectKind, out HashSet? fileReplacements)) + ct.ThrowIfCancellationRequested(); + + var toCompute = fragment.FileReplacements.Where(f => !f.IsFileSwap).ToArray(); + _logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length); + var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray()); + foreach (var file in toCompute) { - var toCompute = fileReplacements.Where(f => !f.IsFileSwap).ToArray(); - _logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length); - var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray()); - foreach (var file in toCompute) - { - file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty; - } - var removed = fileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash)); - if (removed > 0) - { - _logger.LogDebug("Removed {amount} of invalid files", removed); - } + ct.ThrowIfCancellationRequested(); + file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty; + } + var removed = fragment.FileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash)); + if (removed > 0) + { + _logger.LogDebug("Removed {amount} of invalid files", removed); } if (objectKind == ObjectKind.Player) { try { - await VerifyPlayerAnimationBones(boneIndices, data, objectKind).ConfigureAwait(false); + await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false); } catch (Exception e) { @@ -259,10 +253,10 @@ public class PlayerDataFactory _logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds); - return data; + return fragment; } - private async Task VerifyPlayerAnimationBones(Dictionary>? boneIndices, CharacterData previousData, ObjectKind objectKind) + private async Task VerifyPlayerAnimationBones(Dictionary>? boneIndices, CharacterDataFragmentPlayer fragment, CancellationToken ct) { if (boneIndices == null) return; @@ -274,8 +268,10 @@ public class PlayerDataFactory if (boneIndices.All(u => u.Value.Count == 0)) return; int noValidationFailed = 0; - foreach (var file in previousData.FileReplacements[objectKind].Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList()) + foreach (var file in fragment.FileReplacements.Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList()) { + ct.ThrowIfCancellationRequested(); + var skeletonIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false); bool validationFailed = false; if (skeletonIndices != null) @@ -305,10 +301,10 @@ public class PlayerDataFactory { noValidationFailed++; _logger.LogDebug("Removing {file} from sent file replacements and transient data", file.ResolvedPath); - previousData.FileReplacements[objectKind].Remove(file); + fragment.FileReplacements.Remove(file); foreach (var gamePath in file.GamePaths) { - _transientResourceManager.RemoveTransientResource(objectKind, gamePath); + _transientResourceManager.RemoveTransientResource(ObjectKind.Player, gamePath); } } diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 2e9fc66..963b2ca 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -2,7 +2,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; -using MareSynchronos.Utils; using Microsoft.Extensions.Logging; using System.Runtime.CompilerServices; using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer; @@ -10,18 +9,15 @@ using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; namespace MareSynchronos.PlayerData.Handlers; -public sealed class GameObjectHandler : DisposableMediatorSubscriberBase +public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighPriorityMediatorSubscriber { private readonly DalamudUtilService _dalamudUtil; private readonly Func _getAddress; private readonly bool _isOwnedObject; private readonly PerformanceCollectorService _performanceCollector; - private CancellationTokenSource? _clearCts = new(); + private byte _classJob = 0; private Task? _delayedZoningTask; private bool _haltProcessing = false; - private bool _ignoreSendAfterRedraw = false; - private int _ptrNullCounter = 0; - private byte _classJob = 0; private CancellationTokenSource _zoningCts = new(); public GameObjectHandler(ILogger logger, PerformanceCollectorService performanceCollector, @@ -76,12 +72,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase if (msg.Address == Address) { _haltProcessing = false; - _ = Task.Run(async () => - { - _ignoreSendAfterRedraw = true; - await Task.Delay(500).ConfigureAwait(false); - _ignoreSendAfterRedraw = false; - }); } }); @@ -90,22 +80,23 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase _dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult(); } - private enum DrawCondition + public enum DrawCondition { None, + ObjectZero, DrawObjectZero, RenderFlags, ModelInSlotLoaded, ModelFilesInSlotLoaded } - public byte RaceId { get; private set; } - public byte Gender { get; private set; } - public byte TribeId { get; private set; } - public IntPtr Address { get; private set; } + public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None; + public byte Gender { get; private set; } public string Name { get; private set; } public ObjectKind ObjectKind { get; } + public byte RaceId { get; private set; } + public byte TribeId { get; private set; } private byte[] CustomizeData { get; set; } = new byte[26]; private IntPtr DrawObjectAddress { get; set; } private byte[] EquipSlotData { get; set; } = new byte[40]; @@ -116,7 +107,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase { while (await _dalamudUtil.RunOnFrameworkThread(() => { - if (IsBeingDrawn()) return true; + if (_haltProcessing) CheckAndUpdateObject(); + if (CurrentDrawCondition != DrawCondition.None) return true; var gameObj = _dalamudUtil.CreateGameObject(Address); if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara) { @@ -141,12 +133,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } } - public IntPtr CurrentAddress() - { - _dalamudUtil.EnsureIsOnFramework(); - return _getAddress.Invoke(); - } - public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject() { return _dalamudUtil.CreateGameObject(Address); @@ -185,15 +171,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase Address = _getAddress(); if (Address != IntPtr.Zero) { - _ptrNullCounter = 0; var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject; DrawObjectAddress = drawObjAddr; + CurrentDrawCondition = DrawCondition.None; } else { DrawObjectAddress = IntPtr.Zero; + CurrentDrawCondition = DrawCondition.DrawObjectZero; } + CurrentDrawCondition = IsBeingDrawnUnsafe(); + if (_haltProcessing) return; bool drawObjDiff = DrawObjectAddress != prevDrawObj; @@ -201,12 +190,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero) { - if (_clearCts != null) - { - Logger.LogDebug("[{this}] Cancelling Clear Task", this); - _clearCts.CancelDispose(); - _clearCts = null; - } var chara = (Character*)Address; var name = chara->GameObject.NameString; bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); @@ -244,7 +227,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff); } - if (equipDiff && !_isOwnedObject && !_ignoreSendAfterRedraw) // send the message out immediately and cancel out, no reason to continue if not self + if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self { Logger.LogTrace("[{this}] Changed", this); Mediator.Publish(new CharacterChangedMessage(this)); @@ -288,26 +271,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } else if (addrDiff || drawObjDiff) { + CurrentDrawCondition = DrawCondition.DrawObjectZero; Logger.LogTrace("[{this}] Changed", this); if (_isOwnedObject && ObjectKind != ObjectKind.Player) { - _clearCts?.CancelDispose(); - _clearCts = new(); - var token = _clearCts.Token; - _ = Task.Run(() => ClearAsync(token), token); + Mediator.Publish(new ClearCacheForObjectMessage(this)); } } } - private async Task ClearAsync(CancellationToken token) - { - Logger.LogDebug("[{this}] Running Clear Task", this); - await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); - Logger.LogDebug("[{this}] Sending ClearCachedForObjectMessage", this); - Mediator.Publish(new ClearCacheForObjectMessage(this)); - _clearCts = null; - } - private unsafe bool CompareAndUpdateCustomizeData(Span customizeData) { bool hasChanges = false; @@ -382,31 +354,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } } - private unsafe IntPtr GetDrawObjUnsafe(nint curPtr) - { - return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject; - } - private bool IsBeingDrawn() { - var curPtr = _getAddress(); - Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr: {ptr}", this, curPtr.ToString("X")); - - if (curPtr == IntPtr.Zero && _ptrNullCounter < 2) - { - Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr is ZERO, counter is {cnt}", this, _ptrNullCounter); - _ptrNullCounter++; - return true; - } - - if (curPtr == IntPtr.Zero) - { - Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr is ZERO, returning", this); - - Address = IntPtr.Zero; - DrawObjectAddress = IntPtr.Zero; - throw new ArgumentNullException($"CurPtr for {this} turned ZERO"); - } + if (_haltProcessing) CheckAndUpdateObject(); if (_dalamudUtil.IsAnythingDrawing) { @@ -414,27 +364,23 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase return true; } - var drawObj = GetDrawObjUnsafe(curPtr); - Logger.LogTrace("[{this}] IsBeingDrawn, DrawObjPtr: {ptr}", this, drawObj.ToString("X")); - var isDrawn = IsBeingDrawnUnsafe(drawObj, curPtr); - Logger.LogTrace("[{this}] IsBeingDrawn, Condition: {cond}", this, isDrawn); - return isDrawn != DrawCondition.None; + Logger.LogTrace("[{this}] IsBeingDrawn, Condition: {cond}", this, CurrentDrawCondition); + return CurrentDrawCondition != DrawCondition.None; } - private unsafe DrawCondition IsBeingDrawnUnsafe(IntPtr drawObj, IntPtr curPtr) + private unsafe DrawCondition IsBeingDrawnUnsafe() { - var drawObjZero = drawObj == IntPtr.Zero; - if (drawObjZero) return DrawCondition.DrawObjectZero; - var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags) != 0x0; + if (Address == IntPtr.Zero) return DrawCondition.ObjectZero; + if (DrawObjectAddress == IntPtr.Zero) return DrawCondition.DrawObjectZero; + var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->RenderFlags) != 0x0; if (renderFlags) return DrawCondition.RenderFlags; if (ObjectKind == ObjectKind.Player) { - var modelInSlotLoaded = (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0); + var modelInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelInSlotLoaded != 0); if (modelInSlotLoaded) return DrawCondition.ModelInSlotLoaded; - var modelFilesInSlotLoaded = (((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0); + var modelFilesInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelFilesInSlotLoaded != 0); if (modelFilesInSlotLoaded) return DrawCondition.ModelFilesInSlotLoaded; - return DrawCondition.None; } return DrawCondition.None; @@ -442,11 +388,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private void ZoneSwitchEnd() { - if (!_isOwnedObject || _haltProcessing) return; + if (!_isOwnedObject) return; - _clearCts?.Cancel(); - _clearCts?.Dispose(); - _clearCts = null; try { _zoningCts?.CancelAfter(2500); @@ -463,7 +406,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private void ZoneSwitchStart() { - if (!_isOwnedObject || _haltProcessing) return; + if (!_isOwnedObject) return; _zoningCts = new(); Logger.LogDebug("[{obj}] Starting Delay After Zoning", this); diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index a3603c2..bbd8ee6 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -8,37 +8,26 @@ using Microsoft.Extensions.Logging; namespace MareSynchronos.PlayerData.Services; -#pragma warning disable MA0040 - public sealed class CacheCreationService : DisposableMediatorSubscriberBase { private readonly SemaphoreSlim _cacheCreateLock = new(1); - private readonly Dictionary _cachesToCreate = []; + private readonly HashSet _cachesToCreate = []; private readonly PlayerDataFactory _characterDataFactory; - private readonly CancellationTokenSource _cts = new(); + private readonly HashSet _currentlyCreating = []; + private readonly HashSet _debouncedObjectCache = []; private readonly CharacterData _playerData = new(); private readonly Dictionary _playerRelatedObjects = []; - private Task? _cacheCreationTask; - private CancellationTokenSource _honorificCts = new(); - private CancellationTokenSource _petNicknamesCts = new(); - private CancellationTokenSource _moodlesCts = new(); - private bool _isZoning = false; + private readonly CancellationTokenSource _runtimeCts = new(); + private CancellationTokenSource _creationCts = new(); + private CancellationTokenSource _debounceCts = new(); private bool _haltCharaDataCreation; - private readonly Dictionary _glamourerCts = new(); + private bool _isZoning = false; public CacheCreationService(ILogger logger, MareMediator mediator, GameObjectHandlerFactory gameObjectHandlerFactory, PlayerDataFactory characterDataFactory, DalamudUtilService dalamudUtil) : base(logger, mediator) { _characterDataFactory = characterDataFactory; - Mediator.Subscribe(this, (msg) => - { - Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor); - _cacheCreateLock.Wait(); - _cachesToCreate[msg.ObjectToCreateFor.ObjectKind] = msg.ObjectToCreateFor; - _cacheCreateLock.Release(); - }); - Mediator.Subscribe(this, (msg) => _isZoning = true); Mediator.Subscribe(this, (msg) => _isZoning = false); @@ -47,6 +36,12 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase _haltCharaDataCreation = !msg.Resume; }); + Mediator.Subscribe(this, (msg) => + { + Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor); + AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind); + }); + _playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, dalamudUtil.GetPlayerPointer, isWatched: true) .GetAwaiter().GetResult(); _playerRelatedObjects[ObjectKind.MinionOrMount] = gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => dalamudUtil.GetMinionOrMount(), isWatched: true) @@ -58,77 +53,64 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (msg) => { - if (msg.GameObjectHandler != _playerRelatedObjects[ObjectKind.Player]) return; - - Logger.LogTrace("Removing pet data for {obj}", msg.GameObjectHandler); - _playerData.FileReplacements.Remove(ObjectKind.Pet); - _playerData.GlamourerString.Remove(ObjectKind.Pet); - _playerData.CustomizePlusScale.Remove(ObjectKind.Pet); - Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); + if (msg.GameObjectHandler == _playerRelatedObjects[ObjectKind.Player]) + { + AddCacheToCreate(ObjectKind.Player); + AddCacheToCreate(ObjectKind.Pet); + } }); Mediator.Subscribe(this, (msg) => { - // ignore pets - if (msg.ObjectToCreateFor == _playerRelatedObjects[ObjectKind.Pet]) return; - _ = Task.Run(() => + if (msg.ObjectToCreateFor.ObjectKind == ObjectKind.Pet) { - Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor); - _playerData.FileReplacements.Remove(msg.ObjectToCreateFor.ObjectKind); - _playerData.GlamourerString.Remove(msg.ObjectToCreateFor.ObjectKind); - _playerData.CustomizePlusScale.Remove(msg.ObjectToCreateFor.ObjectKind); - Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); - }); + Logger.LogTrace("Received clear cache for {obj}, ignoring", msg.ObjectToCreateFor); + return; + } + Logger.LogDebug("Clearing cache for {obj}", msg.ObjectToCreateFor); + AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind); }); Mediator.Subscribe(this, (msg) => { if (_isZoning) return; - _ = Task.Run(async () => + foreach (var item in _playerRelatedObjects + .Where(item => msg.Address == null + || item.Value.Address == msg.Address).Select(k => k.Key)) { - - foreach (var item in _playerRelatedObjects - .Where(item => msg.Address == null - || item.Value.Address == msg.Address).Select(k => k.Key)) - { - Logger.LogDebug("Received CustomizePlus change, updating {obj}", item); - await AddPlayerCacheToCreate(item).ConfigureAwait(false); - } - }); + Logger.LogDebug("Received CustomizePlus change, updating {obj}", item); + AddCacheToCreate(item); + } }); + Mediator.Subscribe(this, (msg) => { if (_isZoning) return; Logger.LogDebug("Received Heels Offset change, updating player"); - _ = AddPlayerCacheToCreate(); + AddCacheToCreate(); }); + Mediator.Subscribe(this, (msg) => { if (_isZoning) return; var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address); if (changedType.Key != default || changedType.Value != default) { - GlamourerChanged(changedType.Key); + Logger.LogDebug("Received GlamourerChangedMessage for {kind}", changedType); + AddCacheToCreate(changedType.Key); } }); + Mediator.Subscribe(this, (msg) => { if (_isZoning) return; if (!string.Equals(msg.NewHonorificTitle, _playerData.HonorificData, StringComparison.Ordinal)) { Logger.LogDebug("Received Honorific change, updating player"); - HonorificChanged(); - } - }); - Mediator.Subscribe(this, (msg) => - { - if (_isZoning) return; - if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal)) - { - Logger.LogDebug("Received Pet Nicknames change, updating player"); - PetNicknamesChanged(); + AddCacheToCreate(ObjectKind.Player); } }); + Mediator.Subscribe(this, (msg) => { if (_isZoning) return; @@ -136,16 +118,30 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase if (changedType.Key == ObjectKind.Player && changedType.Value != default) { Logger.LogDebug("Received Moodles change, updating player"); - MoodlesChanged(); + AddCacheToCreate(ObjectKind.Player); } }); - Mediator.Subscribe(this, (msg) => + + Mediator.Subscribe(this, (msg) => { - Logger.LogDebug("Received Penumbra Mod settings change, updating player"); - AddPlayerCacheToCreate().GetAwaiter().GetResult(); + if (_isZoning) return; + if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal)) + { + Logger.LogDebug("Received Pet Nicknames change, updating player"); + AddCacheToCreate(ObjectKind.Player); + } }); - Mediator.Subscribe(this, (msg) => ProcessCacheCreation()); + Mediator.Subscribe(this, (msg) => + { + Logger.LogDebug("Received Penumbra Mod settings change, updating everything"); + AddCacheToCreate(ObjectKind.Player); + AddCacheToCreate(ObjectKind.Pet); + AddCacheToCreate(ObjectKind.MinionOrMount); + AddCacheToCreate(ObjectKind.Companion); + }); + + Mediator.Subscribe(this, (msg) => ProcessCacheCreation()); } protected override void Dispose(bool disposing) @@ -153,111 +149,95 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase base.Dispose(disposing); _playerRelatedObjects.Values.ToList().ForEach(p => p.Dispose()); - _cts.Dispose(); + _runtimeCts.Cancel(); + _runtimeCts.Dispose(); + _creationCts.Cancel(); + _creationCts.Dispose(); } - private async Task AddPlayerCacheToCreate(ObjectKind kind = ObjectKind.Player) + private void AddCacheToCreate(ObjectKind kind = ObjectKind.Player) { - await _cacheCreateLock.WaitAsync().ConfigureAwait(false); - _cachesToCreate[kind] = _playerRelatedObjects[kind]; + _debounceCts.Cancel(); + _debounceCts.Dispose(); + _debounceCts = new(); + var token = _debounceCts.Token; + _cacheCreateLock.Wait(); + _debouncedObjectCache.Add(kind); _cacheCreateLock.Release(); - } - - private void GlamourerChanged(ObjectKind kind) - { - if (_glamourerCts.TryGetValue(kind, out var cts)) - { - _glamourerCts[kind]?.Cancel(); - _glamourerCts[kind]?.Dispose(); - } - _glamourerCts[kind] = new(); - var token = _glamourerCts[kind].Token; _ = Task.Run(async () => { - await Task.Delay(TimeSpan.FromMilliseconds(500), token).ConfigureAwait(false); - await AddPlayerCacheToCreate(kind).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); + Logger.LogTrace("Debounce complete, inserting objects to create for: {obj}", string.Join(", ", _debouncedObjectCache)); + await _cacheCreateLock.WaitAsync(token).ConfigureAwait(false); + foreach (var item in _debouncedObjectCache) + { + _cachesToCreate.Add(item); + } + _debouncedObjectCache.Clear(); + _cacheCreateLock.Release(); }); } - private void HonorificChanged() - { - _honorificCts?.Cancel(); - _honorificCts?.Dispose(); - _honorificCts = new(); - var token = _honorificCts.Token; - - _ = Task.Run(async () => - { - await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); - await AddPlayerCacheToCreate().ConfigureAwait(false); - }, token); - } - - private void PetNicknamesChanged() - { - _petNicknamesCts?.Cancel(); - _petNicknamesCts?.Dispose(); - _petNicknamesCts = new(); - var token = _petNicknamesCts.Token; - - _ = Task.Run(async () => - { - await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); - await AddPlayerCacheToCreate().ConfigureAwait(false); - }, token); - } - - private void MoodlesChanged() - { - _moodlesCts?.Cancel(); - _moodlesCts?.Dispose(); - _moodlesCts = new(); - var token = _moodlesCts.Token; - - _ = Task.Run(async () => - { - await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); - await AddPlayerCacheToCreate().ConfigureAwait(false); - }, token); - } - private void ProcessCacheCreation() { if (_isZoning || _haltCharaDataCreation) return; - if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true)) - { - _cacheCreateLock.Wait(); - var toCreate = _cachesToCreate.ToList(); - _cachesToCreate.Clear(); - _cacheCreateLock.Release(); + if (_cachesToCreate.Count == 0) return; - _cacheCreationTask = Task.Run(async () => + if (_playerRelatedObjects.Any(p => p.Value.CurrentDrawCondition is + not (GameObjectHandler.DrawCondition.None or GameObjectHandler.DrawCondition.DrawObjectZero or GameObjectHandler.DrawCondition.ObjectZero))) + { + Logger.LogDebug("Waiting for draw to finish before executing cache creation"); + return; + } + + _creationCts.Cancel(); + _creationCts.Dispose(); + _creationCts = new(); + _cacheCreateLock.Wait(); + var objectKindsToCreate = _cachesToCreate.ToList(); + foreach (var creationObj in objectKindsToCreate) + { + _currentlyCreating.Add(creationObj); + } + _cachesToCreate.Clear(); + _cacheCreateLock.Release(); + + _ = Task.Run(async () => + { + using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_creationCts.Token, _runtimeCts.Token); + + Logger.LogDebug("Creating Caches for {objectKinds}", string.Join(", ", objectKindsToCreate)); + + try { - try + Dictionary createdData = []; + foreach (var objectKind in objectKindsToCreate) { - foreach (var obj in toCreate) - { - await _characterDataFactory.BuildCharacterData(_playerData, obj.Value, _cts.Token).ConfigureAwait(false); - } + createdData[objectKind] = await _characterDataFactory.BuildCharacterData(_playerRelatedObjects[objectKind], linkedCts.Token).ConfigureAwait(false); + } - Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); - } - catch (Exception ex) + foreach (var kvp in createdData) { - Logger.LogCritical(ex, "Error during Cache Creation Processing"); + _playerData.SetFragment(kvp.Key, kvp.Value); } - finally - { - Logger.LogDebug("Cache Creation complete"); - } - }, _cts.Token); - } - else if (_cachesToCreate.Any()) - { - Logger.LogDebug("Cache Creation stored until previous creation finished"); - } + + Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); + _currentlyCreating.Clear(); + } + catch (OperationCanceledException) + { + Logger.LogDebug("Cache Creation cancelled"); + } + catch (Exception ex) + { + Logger.LogCritical(ex, "Error during Cache Creation Processing"); + } + finally + { + Logger.LogDebug("Cache Creation complete"); + } + }); } -} -#pragma warning restore MA0040 \ No newline at end of file +} \ No newline at end of file diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 0725658..d38aaa9 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -150,6 +150,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); @@ -224,7 +225,6 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); - collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); diff --git a/MareSynchronos/Services/CharaData/CharaDataFileHandler.cs b/MareSynchronos/Services/CharaData/CharaDataFileHandler.cs index 6bb1297..3432bbc 100644 --- a/MareSynchronos/Services/CharaData/CharaDataFileHandler.cs +++ b/MareSynchronos/Services/CharaData/CharaDataFileHandler.cs @@ -88,7 +88,8 @@ public sealed class CharaDataFileHandler : IDisposable using var tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtilService.GetCharacterFromObjectTableByIndex(chara.ObjectIndex)?.Address ?? IntPtr.Zero, isWatched: false).ConfigureAwait(false); PlayerData.Data.CharacterData newCdata = new(); - await _playerDataFactory.BuildCharacterData(newCdata, tempHandler, CancellationToken.None).ConfigureAwait(false); + var fragment = await _playerDataFactory.BuildCharacterData(tempHandler, CancellationToken.None).ConfigureAwait(false); + newCdata.SetFragment(ObjectKind.Player, fragment); if (newCdata.FileReplacements.TryGetValue(ObjectKind.Player, out var playerData) && playerData != null) { foreach (var data in playerData.Select(g => g.GamePaths)) diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 91a9503..affe700 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -493,15 +493,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber { if (!_clientState.IsLoggedIn) return; + if (ct == null) + ct = CancellationToken.None; + const int tick = 250; int curWaitTime = 0; try { logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler); - await Task.Delay(tick).ConfigureAwait(true); + await Task.Delay(tick, ct.Value).ConfigureAwait(true); curWaitTime += tick; - while ((!ct?.IsCancellationRequested ?? true) + while ((!ct.Value.IsCancellationRequested) && curWaitTime < timeOut && await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something { diff --git a/MareSynchronos/Services/Mediator/IHighPriorityMediatorSubscriber.cs b/MareSynchronos/Services/Mediator/IHighPriorityMediatorSubscriber.cs new file mode 100644 index 0000000..7660026 --- /dev/null +++ b/MareSynchronos/Services/Mediator/IHighPriorityMediatorSubscriber.cs @@ -0,0 +1,3 @@ +namespace MareSynchronos.Services.Mediator; + +public interface IHighPriorityMediatorSubscriber : IMediatorSubscriber { } \ No newline at end of file diff --git a/MareSynchronos/Services/Mediator/MareMediator.cs b/MareSynchronos/Services/Mediator/MareMediator.cs index ebd0617..54dfede 100644 --- a/MareSynchronos/Services/Mediator/MareMediator.cs +++ b/MareSynchronos/Services/Mediator/MareMediator.cs @@ -91,6 +91,7 @@ public sealed class MareMediator : IHostedService { _messageQueue.Clear(); _loopCts.Cancel(); + _loopCts.Dispose(); return Task.CompletedTask; } @@ -157,7 +158,7 @@ public sealed class MareMediator : IHostedService List subscribersCopy = []; lock (_addRemoveLock) { - subscribersCopy = subscribers?.Where(s => s.Subscriber != null).ToList() ?? []; + subscribersCopy = subscribers?.Where(s => s.Subscriber != null).OrderBy(k => k.Subscriber is IHighPriorityMediatorSubscriber ? 0 : 1).ToList() ?? []; } #pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields @@ -165,7 +166,7 @@ public sealed class MareMediator : IHostedService if (!_genericExecuteMethods.TryGetValue((msgType, message.SubscriberKey), out var methodInfo)) { _genericExecuteMethods[(msgType, message.SubscriberKey)] = methodInfo = GetType() - .GetMethod(nameof(ExecuteReflected), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? + .GetMethod(nameof(ExecuteReflected), BindingFlags.NonPublic | BindingFlags.Instance)? .MakeGenericMethod(msgType); } diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 662b2e7..48d9bb1 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -52,8 +52,8 @@ public record HaltScanMessage(string Source) : MessageBase; public record ResumeScanMessage(string Source) : MessageBase; public record NotificationMessage (string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase; -public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase; -public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase; +public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; +public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage; public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage; public record CharacterDataAnalyzedMessage : MessageBase; public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase;