From 1dfdab4fef119da664ff5d7c29df11a9ba4e21b2 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Tue, 21 Nov 2023 14:59:43 +0100 Subject: [PATCH 01/11] adjust initial dialog to opt in/out into census with buttons --- .../Configurations/ServerConfig.cs | 2 +- .../Components/Popup/BanUserPopupHandler.cs | 6 +-- .../UI/Components/Popup/CensusPopupHandler.cs | 39 +++++++++++++------ .../UI/Components/Popup/IPopupHandler.cs | 3 +- .../UI/Components/Popup/PopupHandler.cs | 16 +++----- .../UI/Components/Popup/ReportPopupHandler.cs | 6 +-- 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/MareSynchronos/MareConfiguration/Configurations/ServerConfig.cs b/MareSynchronos/MareConfiguration/Configurations/ServerConfig.cs index 116c33b..373aab4 100644 --- a/MareSynchronos/MareConfiguration/Configurations/ServerConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/ServerConfig.cs @@ -13,7 +13,7 @@ public class ServerConfig : IMareConfiguration { new ServerStorage() { ServerName = ApiController.MainServer, ServerUri = ApiController.MainServiceUri } }, }; - public bool SendCensusData { get; set; } = true; + public bool SendCensusData { get; set; } = false; public bool ShownCensusPopup { get; set; } = false; public int Version { get; set; } = 1; diff --git a/MareSynchronos/UI/Components/Popup/BanUserPopupHandler.cs b/MareSynchronos/UI/Components/Popup/BanUserPopupHandler.cs index 8a2f69f..1803e78 100644 --- a/MareSynchronos/UI/Components/Popup/BanUserPopupHandler.cs +++ b/MareSynchronos/UI/Components/Popup/BanUserPopupHandler.cs @@ -22,6 +22,8 @@ public class BanUserPopupHandler : IPopupHandler public Vector2 PopupSize => new(500, 250); + public bool ShowClose => true; + public void DrawContent() { UiSharedService.TextWrapped("User " + (_reportedPair.UserData.AliasOrUID) + " will be banned and removed from this Syncshell."); @@ -37,10 +39,6 @@ public class BanUserPopupHandler : IPopupHandler UiSharedService.TextWrapped("The reason will be displayed in the banlist. The current server-side alias if present (Vanity ID) will automatically be attached to the reason."); } - public void OnClose() - { - } - public void Open(OpenBanUserPopupMessage message) { _reportedPair = message.PairToBan; diff --git a/MareSynchronos/UI/Components/Popup/CensusPopupHandler.cs b/MareSynchronos/UI/Components/Popup/CensusPopupHandler.cs index de95e33..8befc4b 100644 --- a/MareSynchronos/UI/Components/Popup/CensusPopupHandler.cs +++ b/MareSynchronos/UI/Components/Popup/CensusPopupHandler.cs @@ -17,13 +17,20 @@ public class CensusPopupHandler : IPopupHandler _uiSharedService = uiSharedService; } - public Vector2 PopupSize => new(600, 350); + private Vector2 _size = new(600, 450); + public Vector2 PopupSize => _size; + + public bool ShowClose => false; public void DrawContent() { + var start = 0f; using (ImRaii.PushFont(_uiSharedService.UidFont)) - UiSharedService.TextWrapped("Mare Census Opt-Out"); - ImGuiHelpers.ScaledDummy(5, 5); + { + start = ImGui.GetCursorPosY() - ImGui.CalcTextSize("Mare Census Data").Y; + UiSharedService.TextWrapped("Mare Census Participation"); + } + ImGuiHelpers.ScaledDummy(5f); UiSharedService.TextWrapped("If you are seeing this popup you are updating from a Mare version that did not collect census data. Please read the following carefully."); ImGui.Separator(); UiSharedService.TextWrapped("Mare Census is a data collecting service that can be used for statistical purposes. " + @@ -34,17 +41,25 @@ public class CensusPopupHandler : IPopupHandler + "- Current Gender (reflecting Glamourer changes)" + Environment.NewLine + "- Current Race (reflecting Glamourer changes)" + Environment.NewLine + "- Current Clan (i.e. Seeker of the Sun, Keeper of the Moon, etc., reflecting Glamourer changes)"); - UiSharedService.TextWrapped("If you do not consent to the data mentioned above being sent, untick the checkbox below."); + UiSharedService.TextWrapped("To consent to collecting census data press the appropriate button below."); UiSharedService.TextWrapped("This setting can be changed anytime in the Mare Settings."); - var sendCensus = _serverConfigurationManager.SendCensusData; - if (ImGui.Checkbox("Allow sending census data", ref sendCensus)) + var width = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + var buttonSize = ImGuiHelpers.GetButtonSize("I consent to send my census data"); + ImGuiHelpers.ScaledDummy(5f); + if (ImGui.Button("I consent to send my census data", new Vector2(width, buttonSize.Y * 2.5f))) { - _serverConfigurationManager.SendCensusData = sendCensus; + _serverConfigurationManager.SendCensusData = true; + _serverConfigurationManager.ShownCensusPopup = true; + ImGui.CloseCurrentPopup(); } - } - - public void OnClose() - { - _serverConfigurationManager.ShownCensusPopup = true; + ImGuiHelpers.ScaledDummy(1f); + if (ImGui.Button("I do not consent to send my census data", new Vector2(width, buttonSize.Y))) + { + _serverConfigurationManager.SendCensusData = false; + _serverConfigurationManager.ShownCensusPopup = true; + ImGui.CloseCurrentPopup(); + } + var height = ImGui.GetCursorPosY() - start; + _size = _size with { Y = height }; } } diff --git a/MareSynchronos/UI/Components/Popup/IPopupHandler.cs b/MareSynchronos/UI/Components/Popup/IPopupHandler.cs index aa649b4..21b99f9 100644 --- a/MareSynchronos/UI/Components/Popup/IPopupHandler.cs +++ b/MareSynchronos/UI/Components/Popup/IPopupHandler.cs @@ -5,8 +5,7 @@ namespace MareSynchronos.UI.Components.Popup; public interface IPopupHandler { Vector2 PopupSize { get; } + bool ShowClose { get; } void DrawContent(); - - void OnClose(); } \ No newline at end of file diff --git a/MareSynchronos/UI/Components/Popup/PopupHandler.cs b/MareSynchronos/UI/Components/Popup/PopupHandler.cs index 055ee91..484659b 100644 --- a/MareSynchronos/UI/Components/Popup/PopupHandler.cs +++ b/MareSynchronos/UI/Components/Popup/PopupHandler.cs @@ -69,17 +69,13 @@ public class PopupHandler : WindowMediatorSubscriberBase using var popup = ImRaii.Popup(WindowName, ImGuiWindowFlags.Modal); if (!popup) return; _currentHandler.DrawContent(); - ImGui.Separator(); - if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Times, "Close")) + if (_currentHandler.ShowClose) { - ImGui.CloseCurrentPopup(); - _currentHandler.OnClose(); + ImGui.Separator(); + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Times, "Close")) + { + ImGui.CloseCurrentPopup(); + } } } - - public override void OnClose() - { - base.OnClose(); - _currentHandler?.OnClose(); - } } \ No newline at end of file diff --git a/MareSynchronos/UI/Components/Popup/ReportPopupHandler.cs b/MareSynchronos/UI/Components/Popup/ReportPopupHandler.cs index 3f58dd2..a93bfd9 100644 --- a/MareSynchronos/UI/Components/Popup/ReportPopupHandler.cs +++ b/MareSynchronos/UI/Components/Popup/ReportPopupHandler.cs @@ -24,6 +24,8 @@ internal class ReportPopupHandler : IPopupHandler public Vector2 PopupSize => new(500, 500); + public bool ShowClose => true; + public void DrawContent() { using (ImRaii.PushFont(_uiSharedService.UidFont)) @@ -49,10 +51,6 @@ internal class ReportPopupHandler : IPopupHandler } } - public void OnClose() - { - } - public void Open(OpenReportPopupMessage msg) { _reportedPair = msg.PairToReport; From 717c51e280b572d9d00cada57178ac33fd289426 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Tue, 21 Nov 2023 15:01:40 +0100 Subject: [PATCH 02/11] add experimental resolving of data through penumbra --- .../Configurations/MareConfig.cs | 1 + .../PlayerData/Factories/PlayerDataFactory.cs | 30 ++++++++++++------- MareSynchronos/UI/SettingsUi.cs | 10 +++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs index 8392327..46a8adc 100644 --- a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs @@ -50,6 +50,7 @@ public class MareConfig : IMareConfiguration public int TransferBarsWidth { get; set; } = 250; public bool UseAlternativeFileUpload { get; set; } = false; public bool UseCompactor { get; set; } = false; + public bool ExperimentalUsePenumbraResourceTree { get; set; } = false; public int Version { get; set; } = 1; public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both; } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 30700fb..fb4bbbc 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; using MareSynchronos.API.Data.Enum; using MareSynchronos.FileCache; using MareSynchronos.Interop; +using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Data; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; @@ -25,11 +26,12 @@ public class PlayerDataFactory private readonly IpcManager _ipcManager; private readonly ILogger _logger; private readonly PerformanceCollectorService _performanceCollector; + private readonly MareConfigService _mareConfigService; private readonly TransientResourceManager _transientResourceManager; public PlayerDataFactory(ILogger logger, DalamudUtilService dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory, - PerformanceCollectorService performanceCollector) + PerformanceCollectorService performanceCollector, MareConfigService mareConfigService) { _logger = logger; _dalamudUtil = dalamudUtil; @@ -37,7 +39,7 @@ public class PlayerDataFactory _transientResourceManager = transientResourceManager; _fileCacheManager = fileReplacementFactory; _performanceCollector = performanceCollector; - + _mareConfigService = mareConfigService; _logger.LogTrace("Creating " + nameof(PlayerDataFactory)); } @@ -281,7 +283,7 @@ public class PlayerDataFactory AddReplacementsFromRenderModel(mdl, forwardResolve, reverseResolve); } - if (objectKind == ObjectKind.Player) + if (objectKind == ObjectKind.Player && human->CharacterBase.GetModelType() == CharacterBase.ModelType.Human) { AddPlayerSpecificReplacements(human, forwardResolve, reverseResolve); } @@ -330,12 +332,18 @@ public class PlayerDataFactory Stopwatch st = Stopwatch.StartNew(); // penumbra call, it's currently broken - // var data = (await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0]; - // if (data == null) throw new InvalidOperationException("Penumbra returned null data"); - - // gather static replacements from render model - var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false); - Dictionary> resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false); + IReadOnlyDictionary? resolvedPaths; + if (_mareConfigService.Current.ExperimentalUsePenumbraResourceTree) + { + resolvedPaths = (await _ipcManager.PenumbraGetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false))![0]; + if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data"); + } + else + { + // gather static replacements from render model + var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false); + resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false); + } previousData.FileReplacements[objectKind] = new HashSet(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance) .Where(p => p.HasFileReplacement).ToHashSet(); @@ -425,7 +433,7 @@ public class PlayerDataFactory return previousData; } - private async Task>> GetFileReplacementsFromPaths(HashSet forwardResolve, HashSet reverseResolve) + private async Task> GetFileReplacementsFromPaths(HashSet forwardResolve, HashSet reverseResolve) { var forwardPaths = forwardResolve.ToArray(); var reversePaths = reverseResolve.ToArray(); @@ -457,7 +465,7 @@ public class PlayerDataFactory } } - return resolvedPaths; + return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly(); } private HashSet ManageSemiTransientData(ObjectKind objectKind, IntPtr charaPointer) diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index ce0a22b..aaea12a 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -549,6 +549,16 @@ public class SettingsUi : WindowMediatorSubscriberBase } _lastTab = "General"; + UiSharedService.FontText("Experimental", _uiShared.UidFont); + var usePenumbraResolve = _configService.Current.ExperimentalUsePenumbraResourceTree; + if (ImGui.Checkbox("Use Penumbra to resolve character", ref usePenumbraResolve)) + { + _configService.Current.ExperimentalUsePenumbraResourceTree = usePenumbraResolve; + _configService.Save(); + } + UiSharedService.DrawHelpText("Requires Penumbra version greater equal to 0.8.1.9 - please report issues with that feature to the Penumbra Discord"); + ImGui.Separator(); + UiSharedService.FontText("Notes", _uiShared.UidFont); if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard")) { From fd8b5c03919678c482b834f9954b7224cec5364c Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 23 Nov 2023 20:01:01 +0100 Subject: [PATCH 03/11] add file storage validation --- MareSynchronos/FileCache/FileCacheManager.cs | 46 +++++++++++++++++- MareSynchronos/UI/SettingsUi.cs | 51 ++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/MareSynchronos/FileCache/FileCacheManager.cs b/MareSynchronos/FileCache/FileCacheManager.cs index 6d56416..dffc67f 100644 --- a/MareSynchronos/FileCache/FileCacheManager.cs +++ b/MareSynchronos/FileCache/FileCacheManager.cs @@ -1,6 +1,7 @@ using LZ4; using MareSynchronos.Interop; using MareSynchronos.MareConfiguration; +using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; @@ -15,17 +16,19 @@ public sealed class FileCacheManager : IDisposable public const string CsvSplit = "|"; public const string PenumbraPrefix = "{penumbra}"; private readonly MareConfigService _configService; + private readonly MareMediator _mareMediator; private readonly string _csvPath; private readonly ConcurrentDictionary> _fileCaches = new(StringComparer.Ordinal); private readonly object _fileWriteLock = new(); private readonly IpcManager _ipcManager; private readonly ILogger _logger; - public FileCacheManager(ILogger logger, IpcManager ipcManager, MareConfigService configService) + public FileCacheManager(ILogger logger, IpcManager ipcManager, MareConfigService configService, MareMediator mareMediator) { _logger = logger; _ipcManager = ipcManager; _configService = configService; + _mareMediator = mareMediator; _csvPath = Path.Combine(configService.ConfigurationDirectory, "FileCache.csv"); lock (_fileWriteLock) @@ -172,6 +175,47 @@ public sealed class FileCacheManager : IDisposable return output; } + public Task> ValidateLocalIntegrity(IProgress<(int, int, FileCacheEntity)> progress, CancellationToken cancellationToken) + { + _mareMediator.Publish(new HaltScanMessage("IntegrityCheck")); + _logger.LogInformation("Validating local storage"); + var cacheEntries = _fileCaches.SelectMany(v => v.Value).Where(v => v.IsCacheEntry).ToList(); + List brokenEntities = new(); + int i = 0; + foreach (var fileCache in cacheEntries) + { + _logger.LogInformation("Validating {file}", fileCache.ResolvedFilepath); + + progress.Report((i, cacheEntries.Count, fileCache)); + i++; + var computedHash = Crypto.GetFileHash(fileCache.ResolvedFilepath); + if (!string.Equals(computedHash, fileCache.Hash, StringComparison.Ordinal)) + { + _logger.LogInformation("Failed to validate {file}, got hash {hash}, expected hash {hash}", fileCache.ResolvedFilepath, computedHash, fileCache.Hash); + brokenEntities.Add(fileCache); + } + + if (cancellationToken.IsCancellationRequested) break; + } + + foreach (var brokenEntity in brokenEntities) + { + RemoveHashedFile(brokenEntity.Hash, brokenEntity.PrefixedFilePath); + + try + { + File.Delete(brokenEntity.ResolvedFilepath); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Could not delete {file}", brokenEntity.ResolvedFilepath); + } + } + + _mareMediator.Publish(new ResumeScanMessage("IntegrityCheck")); + return Task.FromResult(brokenEntities); + } + public string GetCacheFilePath(string hash, string extension) { return Path.Combine(_configService.Current.CacheFolder, hash + "." + extension); diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index aaea12a..c5f4f1c 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -35,6 +35,7 @@ public class SettingsUi : WindowMediatorSubscriberBase private readonly FileCompactor _fileCompactor; private readonly FileUploadManager _fileTransferManager; private readonly FileTransferOrchestrator _fileTransferOrchestrator; + private readonly FileCacheManager _fileCacheManager; private readonly MareCharaFileManager _mareCharaFileManager; private readonly PairManager _pairManager; private readonly PerformanceCollectorService _performanceCollector; @@ -49,6 +50,10 @@ public class SettingsUi : WindowMediatorSubscriberBase private bool _readClearCache = false; private bool _readExport = false; private bool _wasOpen = false; + private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress; + private Task>? _validationTask; + private CancellationTokenSource? _validationCts; + private (int, int, FileCacheEntity) _currentProgress; public SettingsUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, @@ -57,6 +62,7 @@ public class SettingsUi : WindowMediatorSubscriberBase MareMediator mediator, PerformanceCollectorService performanceCollector, FileUploadManager fileTransferManager, FileTransferOrchestrator fileTransferOrchestrator, + FileCacheManager fileCacheManager, FileCompactor fileCompactor, ApiController apiController) : base(logger, mediator, "Mare Synchronos Settings") { _configService = configService; @@ -66,11 +72,13 @@ public class SettingsUi : WindowMediatorSubscriberBase _performanceCollector = performanceCollector; _fileTransferManager = fileTransferManager; _fileTransferOrchestrator = fileTransferOrchestrator; + _fileCacheManager = fileCacheManager; _apiController = apiController; _fileCompactor = fileCompactor; _uiShared = uiShared; AllowClickthrough = false; AllowPinning = false; + _validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v); SizeConstraints = new WindowSizeConstraints() { @@ -509,6 +517,49 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.EndDisabled(); ImGui.TextUnformatted("The file compactor is only available on Windows."); } + ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); + + ImGui.Separator(); + UiSharedService.TextWrapped("File Storage validation can make sure that all files in your local Mare Storage are valid. " + + "Run the validation before you clear the Storage for no reason. " + Environment.NewLine + + "This operation, depending on how many files you have in your storage, can take a while and will be CPU and drive intensive."); + using (ImRaii.Disabled(_validationTask != null && !_validationTask.IsCompleted)) + { + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Check, "Start File Storage Validation")) + { + _validationCts?.Cancel(); + _validationCts?.Dispose(); + _validationCts = new(); + var token = _validationCts.Token; + _validationTask = Task.Run(() => _fileCacheManager.ValidateLocalIntegrity(_validationProgress, token)); + } + } + if (_validationTask != null && !_validationTask.IsCompleted) + { + ImGui.SameLine(); + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Times, "Cancel")) + { + _validationCts?.Cancel(); + } + } + + if (_validationTask != null) + { + using (ImRaii.PushIndent(20f)) + { + if (_validationTask.IsCompleted) + { + UiSharedService.TextWrapped($"The storage validation has completed and removed {_validationTask.Result.Count} invalid files from storage."); + } + else + { + + UiSharedService.TextWrapped($"Storage validation is running: {_currentProgress.Item1}/{_currentProgress.Item2}"); + UiSharedService.TextWrapped($"Current item: {_currentProgress.Item3.ResolvedFilepath}"); + } + } + } + ImGui.Separator(); ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); ImGui.TextUnformatted("To clear the local storage accept the following disclaimer"); From 5d20936a059c418d1334f9464cb90a5dc05703c1 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 23 Nov 2023 22:22:39 +0100 Subject: [PATCH 04/11] change tooltip for penumbra version to 0.8.2.1 --- MareSynchronos/UI/SettingsUi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index c5f4f1c..714101e 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -607,7 +607,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ExperimentalUsePenumbraResourceTree = usePenumbraResolve; _configService.Save(); } - UiSharedService.DrawHelpText("Requires Penumbra version greater equal to 0.8.1.9 - please report issues with that feature to the Penumbra Discord"); + UiSharedService.DrawHelpText("Requires Penumbra version greater equal to 0.8.2.1 - please report issues with that feature to the Penumbra Discord"); ImGui.Separator(); UiSharedService.FontText("Notes", _uiShared.UidFont); From a205c25e3d88391f3bdbaae70089a39dee6475da Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sun, 26 Nov 2023 13:02:44 +0100 Subject: [PATCH 05/11] add click to target in ui --- MareSynchronos/Plugin.cs | 5 +- MareSynchronos/Services/DalamudUtilService.cs | 52 ++++++++++++------- MareSynchronos/Services/Mediator/Messages.cs | 1 + MareSynchronos/UI/Components/DrawUserPair.cs | 7 ++- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index dd40218..8c2b03c 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -1,4 +1,5 @@ using Dalamud.ContextMenu; +using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.Windowing; using Dalamud.Plugin; @@ -33,7 +34,7 @@ public sealed class Plugin : IDalamudPlugin public Plugin(DalamudPluginInterface pluginInterface, ICommandManager commandManager, IDataManager gameData, IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui, - IGameGui gameGui, IDtrBar dtrBar, IPluginLog pluginLog) + IGameGui gameGui, IDtrBar dtrBar, IPluginLog pluginLog, ITargetManager targetManager) { _hostBuilderRunTask = new HostBuilder() .UseContentRoot(pluginInterface.ConfigDirectory.FullName) @@ -77,7 +78,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton((s) => new DalamudContextMenu(pluginInterface)); collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService>(), - clientState, objectTable, framework, gameGui, condition, gameData, + clientState, objectTable, framework, gameGui, condition, gameData, targetManager, s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService>(), dtrBar, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 0306e1c..72f3fce 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -1,4 +1,5 @@ using Dalamud.Game.ClientState.Conditions; +using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; @@ -15,7 +16,7 @@ using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace MareSynchronos.Services; -public class DalamudUtilService : IHostedService +public class DalamudUtilService : IHostedService, IMediatorSubscriber { private readonly List _classJobIdsIgnoredForPets = [30]; private readonly IClientState _clientState; @@ -23,7 +24,6 @@ public class DalamudUtilService : IHostedService private readonly IFramework _framework; private readonly IGameGui _gameGui; private readonly ILogger _logger; - private readonly MareMediator _mediator; private readonly IObjectTable _objectTable; private readonly PerformanceCollectorService _performanceCollector; private uint? _classJobId = 0; @@ -35,7 +35,7 @@ public class DalamudUtilService : IHostedService private bool _sentBetweenAreas = false; public DalamudUtilService(ILogger logger, IClientState clientState, IObjectTable objectTable, IFramework framework, - IGameGui gameGui, ICondition condition, IDataManager gameData, MareMediator mediator, PerformanceCollectorService performanceCollector) + IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, MareMediator mediator, PerformanceCollectorService performanceCollector) { _logger = logger; _clientState = clientState; @@ -43,7 +43,7 @@ public class DalamudUtilService : IHostedService _framework = framework; _gameGui = gameGui; _condition = condition; - _mediator = mediator; + Mediator = mediator; _performanceCollector = performanceCollector; WorldData = new(() => { @@ -51,6 +51,17 @@ public class DalamudUtilService : IHostedService .Where(w => w.IsPublic && !w.Name.RawData.IsEmpty) .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); }); + mediator.Subscribe(this, async (msg) => + { + var name = msg.Pair.PlayerName; + if (string.IsNullOrEmpty(name)) return; + var addr = _playerCharas.FirstOrDefault(f => string.Equals(f.Value.Name, name, StringComparison.Ordinal)).Value.Address; + if (addr == nint.Zero) return; + await RunOnFrameworkThread(() => + { + targetManager.Target = CreateGameObject(addr); + }).ConfigureAwait(false); + }); } public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget; @@ -64,6 +75,8 @@ public class DalamudUtilService : IHostedService public Lazy> WorldData { get; private set; } + public MareMediator Mediator { get; } + public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference) { EnsureIsOnFramework(); @@ -279,6 +292,7 @@ public class DalamudUtilService : IHostedService { _logger.LogTrace("Stopping {type}", GetType()); + Mediator.UnsubscribeAll(this); _framework.Update -= FrameworkOnUpdate; return Task.CompletedTask; } @@ -433,31 +447,31 @@ public class DalamudUtilService : IHostedService { _logger.LogDebug("Gpose start"); IsInGpose = true; - _mediator.Publish(new GposeStartMessage()); + Mediator.Publish(new GposeStartMessage()); } else if (GposeTarget == null && IsInGpose) { _logger.LogDebug("Gpose end"); IsInGpose = false; - _mediator.Publish(new GposeEndMessage()); + Mediator.Publish(new GposeEndMessage()); } if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) { _logger.LogDebug("Cutscene start"); IsInCutscene = true; - _mediator.Publish(new CutsceneStartMessage()); - _mediator.Publish(new HaltScanMessage("Cutscene")); + Mediator.Publish(new CutsceneStartMessage()); + Mediator.Publish(new HaltScanMessage("Cutscene")); } else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene) { _logger.LogDebug("Cutscene end"); IsInCutscene = false; - _mediator.Publish(new CutsceneEndMessage()); - _mediator.Publish(new ResumeScanMessage("Cutscene")); + Mediator.Publish(new CutsceneEndMessage()); + Mediator.Publish(new ResumeScanMessage("Cutscene")); } - if (IsInCutscene) { _mediator.Publish(new CutsceneFrameworkUpdateMessage()); return; } + if (IsInCutscene) { Mediator.Publish(new CutsceneFrameworkUpdateMessage()); return; } if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]) { @@ -469,8 +483,8 @@ public class DalamudUtilService : IHostedService { _logger.LogDebug("Zone switch/Gpose start"); _sentBetweenAreas = true; - _mediator.Publish(new ZoneSwitchStartMessage()); - _mediator.Publish(new HaltScanMessage("Zone switch")); + Mediator.Publish(new ZoneSwitchStartMessage()); + Mediator.Publish(new HaltScanMessage("Zone switch")); } } @@ -481,11 +495,11 @@ public class DalamudUtilService : IHostedService { _logger.LogDebug("Zone switch/Gpose end"); _sentBetweenAreas = false; - _mediator.Publish(new ZoneSwitchEndMessage()); - _mediator.Publish(new ResumeScanMessage("Zone switch")); + Mediator.Publish(new ZoneSwitchEndMessage()); + Mediator.Publish(new ResumeScanMessage("Zone switch")); } - _mediator.Publish(new FrameworkUpdateMessage()); + Mediator.Publish(new FrameworkUpdateMessage()); if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; @@ -496,16 +510,16 @@ public class DalamudUtilService : IHostedService _logger.LogDebug("Logged in"); IsLoggedIn = true; _lastZone = _clientState.TerritoryType; - _mediator.Publish(new DalamudLoginMessage()); + Mediator.Publish(new DalamudLoginMessage()); } else if (localPlayer == null && IsLoggedIn) { _logger.LogDebug("Logged out"); IsLoggedIn = false; - _mediator.Publish(new DalamudLogoutMessage()); + Mediator.Publish(new DalamudLogoutMessage()); } - _mediator.Publish(new DelayedFrameworkUpdateMessage()); + Mediator.Publish(new DelayedFrameworkUpdateMessage()); _delayedFrameworkUpdateCheck = DateTime.Now; } diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 3d2f6f4..d5f8c02 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -77,6 +77,7 @@ public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase; public record OpenPermissionWindow(Pair Pair) : MessageBase; public record DownloadLimitChangedMessage() : SameThreadMessage; public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase; +public record TargetPairMessage(Pair Pair) : MessageBase; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs index dbb8466..fd66a09 100644 --- a/MareSynchronos/UI/Components/DrawUserPair.cs +++ b/MareSynchronos/UI/Components/DrawUserPair.cs @@ -208,7 +208,11 @@ public class DrawUserPair else if (_pair.IsVisible) { UiSharedService.NormalizedIcon(FontAwesomeIcon.Eye, ImGuiColors.ParsedGreen); - userPairText = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName; + userPairText = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player"; + if (ImGui.IsItemClicked()) + { + _mediator.Publish(new TargetPairMessage(_pair)); + } } else { @@ -243,6 +247,7 @@ public class DrawUserPair return "Paired through " + groupString; })); } + UiSharedService.AttachToolTip(userPairText); ImGui.SameLine(); From e83d3465849d36b9df59752ebe26dee8be761e2a Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sun, 26 Nov 2023 19:20:17 +0100 Subject: [PATCH 06/11] disable target in pvp --- MareSynchronos/Services/DalamudUtilService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 72f3fce..1dbb040 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -53,6 +53,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber }); mediator.Subscribe(this, async (msg) => { + if (clientState.IsPvP) return; var name = msg.Pair.PlayerName; if (string.IsNullOrEmpty(name)) return; var addr = _playerCharas.FirstOrDefault(f => string.Equals(f.Value.Name, name, StringComparison.Ordinal)).Value.Address; From 26b8daeef9ca69cfcecea7008af0aa8cfa11b8f8 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 30 Nov 2023 00:19:49 +0100 Subject: [PATCH 07/11] add lock around adding to cached handled paths --- .../FileCache/TransientResourceManager.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/MareSynchronos/FileCache/TransientResourceManager.cs b/MareSynchronos/FileCache/TransientResourceManager.cs index 00329f6..3cd3417 100644 --- a/MareSynchronos/FileCache/TransientResourceManager.cs +++ b/MareSynchronos/FileCache/TransientResourceManager.cs @@ -17,6 +17,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"]; private readonly HashSet _playerRelatedPointers = []; private HashSet _cachedFrameAddresses = []; + private readonly object _cacheAdditionLock = new(); public TransientResourceManager(ILogger logger, TransientConfigService configurationService, DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator) @@ -189,7 +190,10 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase private void DalamudUtil_FrameworkUpdate() { _cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress()).ToHashSet(); - _cachedHandledPaths.Clear(); + lock (_cacheAdditionLock) + { + _cachedHandledPaths.Clear(); + } foreach (var item in TransientResources.Where(item => !_dalamudUtil.IsGameObjectPresent(item.Key)).Select(i => i.Key).ToList()) { Logger.LogDebug("Object not present anymore: {addr}", item.ToString("X")); @@ -218,7 +222,10 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase // ignore files already processed this frame if (_cachedHandledPaths.Contains(gamePath)) return; - _cachedHandledPaths.Add(gamePath); + lock (_cacheAdditionLock) + { + _cachedHandledPaths.Add(gamePath); + } // replace individual mtrl stuff if (filePath.StartsWith("|", StringComparison.OrdinalIgnoreCase)) @@ -235,14 +242,20 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase // ignore files to not handle if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase))) { - _cachedHandledPaths.Add(gamePath); + lock (_cacheAdditionLock) + { + _cachedHandledPaths.Add(gamePath); + } return; } // ignore files not belonging to anything player related if (!_cachedFrameAddresses.Contains(gameObject)) { - _cachedHandledPaths.Add(gamePath); + lock (_cacheAdditionLock) + { + _cachedHandledPaths.Add(gamePath); + } return; } From bcf0d422b89cc66d70245e0ede73158b1a4c7905 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sat, 2 Dec 2023 20:23:44 +0100 Subject: [PATCH 08/11] fix bug --- MareSynchronos/Interop/IpcManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index 9dbad06..aaab30c 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -277,12 +277,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase var gameObj = _dalamudUtil.CreateGameObject(character); if (gameObj is Character c) { - var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c); - byte[] bytes = Convert.FromBase64String(glamourerString); - // ignore transparency - bytes[88] = 128; - bytes[89] = 63; - return Convert.ToBase64String(bytes); + return _glamourerGetAllCustomization!.InvokeFunc(c); } return string.Empty; }).ConfigureAwait(false); @@ -667,7 +662,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase try { var version = _glamourerApiVersions.InvokeFunc(); - if (version.Item1 == 0 && version.Item2 >= 1) + bool versionValid = (_pi.InstalledPlugins + .FirstOrDefault(p => string.Equals(p.InternalName, "Glamourer", StringComparison.OrdinalIgnoreCase)) + ?.Version ?? new Version(0, 0, 0, 0)) >= new Version(1, 0, 6, 1); + if (version.Item1 == 0 && version.Item2 >= 1 && versionValid) { apiAvailable = true; } From bd32a55ce5ddaa4edc66370809fffa553e14505f Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Tue, 5 Dec 2023 23:30:19 +0100 Subject: [PATCH 09/11] disable data application and scanner in combat --- MareSynchronos/FileCache/FileCacheManager.cs | 4 +-- .../FileCache/TransientResourceManager.cs | 2 +- MareSynchronos/Interop/IpcManager.cs | 4 +-- .../Factories/PairHandlerFactory.cs | 7 ++-- .../PlayerData/Factories/PlayerDataFactory.cs | 1 + .../PlayerData/Handlers/PairHandler.cs | 31 +++++++++++------ .../Services/CacheCreationService.cs | 13 ++++++++ MareSynchronos/Services/CharacterAnalyzer.cs | 29 ++++++++++------ MareSynchronos/Services/DalamudUtilService.cs | 33 +++++++++++++++---- MareSynchronos/Services/Mediator/Messages.cs | 4 ++- .../Services/PerformanceCollectorService.cs | 2 +- .../WebAPI/Files/FileDownloadManager.cs | 4 +-- .../WebAPI/Files/ThrottledStream.cs | 4 +-- 13 files changed, 95 insertions(+), 43 deletions(-) diff --git a/MareSynchronos/FileCache/FileCacheManager.cs b/MareSynchronos/FileCache/FileCacheManager.cs index dffc67f..853cc05 100644 --- a/MareSynchronos/FileCache/FileCacheManager.cs +++ b/MareSynchronos/FileCache/FileCacheManager.cs @@ -177,7 +177,7 @@ public sealed class FileCacheManager : IDisposable public Task> ValidateLocalIntegrity(IProgress<(int, int, FileCacheEntity)> progress, CancellationToken cancellationToken) { - _mareMediator.Publish(new HaltScanMessage("IntegrityCheck")); + _mareMediator.Publish(new HaltScanMessage(nameof(ValidateLocalIntegrity))); _logger.LogInformation("Validating local storage"); var cacheEntries = _fileCaches.SelectMany(v => v.Value).Where(v => v.IsCacheEntry).ToList(); List brokenEntities = new(); @@ -212,7 +212,7 @@ public sealed class FileCacheManager : IDisposable } } - _mareMediator.Publish(new ResumeScanMessage("IntegrityCheck")); + _mareMediator.Publish(new ResumeScanMessage(nameof(ValidateLocalIntegrity))); return Task.FromResult(brokenEntities); } diff --git a/MareSynchronos/FileCache/TransientResourceManager.cs b/MareSynchronos/FileCache/TransientResourceManager.cs index 3cd3417..e794413 100644 --- a/MareSynchronos/FileCache/TransientResourceManager.cs +++ b/MareSynchronos/FileCache/TransientResourceManager.cs @@ -30,7 +30,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (_) => DalamudUtil_FrameworkUpdate()); Mediator.Subscribe(this, (msg) => { - if (_playerRelatedPointers.Contains(msg.gameObjectHandler)) + if (_playerRelatedPointers.Contains(msg.GameObjectHandler)) { DalamudUtil_ClassJobChanged(); } diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index aaab30c..91604f1 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -492,7 +492,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase { if (!CheckPenumbraApi()) return; - Mediator.Publish(new HaltScanMessage("TextureConversion")); + Mediator.Publish(new HaltScanMessage(nameof(PenumbraConvertTextureFiles))); int currentTexture = 0; foreach (var texture in textures) { @@ -519,7 +519,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase } } } - Mediator.Publish(new ResumeScanMessage("TextureConversion")); + Mediator.Publish(new ResumeScanMessage(nameof(PenumbraConvertTextureFiles))); await _dalamudUtil.RunOnFrameworkThread(async () => { diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index 73bad33..f9ee5f6 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -1,7 +1,6 @@ using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop; -using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; @@ -21,13 +20,12 @@ public class PairHandlerFactory private readonly IpcManager _ipcManager; private readonly ILoggerFactory _loggerFactory; private readonly MareMediator _mareMediator; - private readonly MareConfigService _mareConfigService; private readonly PluginWarningNotificationService _pluginWarningNotificationManager; public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, - FileCacheManager fileCacheManager, MareMediator mareMediator, MareConfigService mareConfigService) + FileCacheManager fileCacheManager, MareMediator mareMediator) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -38,13 +36,12 @@ public class PairHandlerFactory _hostApplicationLifetime = hostApplicationLifetime; _fileCacheManager = fileCacheManager; _mareMediator = mareMediator; - _mareConfigService = mareConfigService; } public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto) { return new PairHandler(_loggerFactory.CreateLogger(), onlineUserIdentDto, _gameObjectHandlerFactory, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, - _fileCacheManager, _mareMediator, _mareConfigService); + _fileCacheManager, _mareMediator); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index fb4bbbc..3f594b3 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -361,6 +361,7 @@ public class PlayerDataFactory { foreach (var item in previousData.FileReplacements[objectKind].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths)) { + _logger.LogDebug("Persisting {item}", item); _transientResourceManager.AddSemiTransientResource(objectKind, item); } } diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 21593d4..e5376e2 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -2,7 +2,6 @@ using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop; -using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; @@ -22,7 +21,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private readonly DalamudUtilService _dalamudUtil; private readonly FileDownloadManager _downloadManager; private readonly FileCacheManager _fileDbManager; - private readonly MareConfigService _mareConfigService; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly IpcManager _ipcManager; private readonly IHostApplicationLifetime _lifetime; @@ -44,8 +42,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase IpcManager ipcManager, FileDownloadManager transferManager, PluginWarningNotificationService pluginWarningNotificationManager, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, - FileCacheManager fileDbManager, MareMediator mediator, - MareConfigService mareConfigService) : base(logger, mediator) + FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) { OnlineUser = onlineUser; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -55,7 +52,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _dalamudUtil = dalamudUtil; _lifetime = lifetime; _fileDbManager = fileDbManager; - _mareConfigService = mareConfigService; _penumbraCollection = _ipcManager.PenumbraCreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); Mediator.Subscribe(this, (_) => FrameworkUpdate()); @@ -77,11 +73,24 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (msg) => { - if (msg.gameObjectHandler == _charaHandler) + if (msg.GameObjectHandler == _charaHandler) { _redrawOnNextApplication = true; } }); + Mediator.Subscribe(this, (msg) => + { + if (IsVisible && _cachedData != null) + { + Guid g = Guid.NewGuid(); + ApplyCharacterData(g, _cachedData, true); + } + }); + Mediator.Subscribe(this, _ => + { + _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate(); + _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); + }); LastAppliedDataSize = -1; } @@ -108,15 +117,17 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) { - if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) + if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero) || _dalamudUtil.IsInCombat) { - Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", - applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); + Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, inCombat: {inCombat}, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", + applicationBase, _dalamudUtil.IsInCombat, _charaHandler == null, PlayerCharacter == IntPtr.Zero); var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, - this, forceApplyCustomization, forceApplyMods: false).Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); + this, forceApplyCustomization, forceApplyMods: false) + .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); _cachedData = characterData; Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods); + if (_dalamudUtil.IsInCombat) SetUploading(isUploading: false); return; } diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index 9a80d18..3790bf6 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -48,8 +48,21 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase _playerRelatedObjects[ObjectKind.Companion] = gameObjectHandlerFactory.Create(ObjectKind.Companion, () => dalamudUtil.GetCompanion(), isWatched: true) .GetAwaiter().GetResult(); + 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())); + }); + Mediator.Subscribe(this, (msg) => { + // ignore pets + if (msg.ObjectToCreateFor == _playerRelatedObjects[ObjectKind.Pet]) return; _ = Task.Run(() => { Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor); diff --git a/MareSynchronos/Services/CharacterAnalyzer.cs b/MareSynchronos/Services/CharacterAnalyzer.cs index 6ae1b25..29cd776 100644 --- a/MareSynchronos/Services/CharacterAnalyzer.cs +++ b/MareSynchronos/Services/CharacterAnalyzer.cs @@ -51,18 +51,27 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable CurrentFile = 1; Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count); - Mediator.Publish(new HaltScanMessage("CharacterAnalyzer")); - - foreach (var file in remaining) + Mediator.Publish(new HaltScanMessage(nameof(CharacterAnalyzer))); + try { - Logger.LogDebug("Computing file {file}", file.FilePaths[0]); - await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); - CurrentFile++; + foreach (var file in remaining) + { + Logger.LogDebug("Computing file {file}", file.FilePaths[0]); + await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); + CurrentFile++; + } + + _fileCacheManager.WriteOutFullCsv(); + + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Failed to analyze files"); + } + finally + { + Mediator.Publish(new ResumeScanMessage(nameof(CharacterAnalyzer))); } - - _fileCacheManager.WriteOutFullCsv(); - - Mediator.Publish(new ResumeScanMessage("CharacterAnalzyer")); } Mediator.Publish(new CharacterDataAnalyzedMessage()); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 1dbb040..4f5c812 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -35,7 +35,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private bool _sentBetweenAreas = false; public DalamudUtilService(ILogger logger, IClientState clientState, IObjectTable objectTable, IFramework framework, - IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, MareMediator mediator, PerformanceCollectorService performanceCollector) + IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, + MareMediator mediator, PerformanceCollectorService performanceCollector) { _logger = logger; _clientState = clientState; @@ -73,6 +74,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber public bool IsLoggedIn { get; private set; } public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread; public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; + public bool IsInCombat { get; private set; } = false; public Lazy> WorldData { get; private set; } @@ -457,19 +459,34 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new GposeEndMessage()); } + if (_condition[ConditionFlag.InCombat] && !IsInCombat) + { + _logger.LogDebug("Combat start"); + IsInCombat = true; + Mediator.Publish(new CombatStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInCombat))); + } + else if (!_condition[ConditionFlag.InCombat] && IsInCombat) + { + _logger.LogDebug("Combat end"); + IsInCombat = false; + Mediator.Publish(new CombatEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCombat))); + } + if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) { _logger.LogDebug("Cutscene start"); IsInCutscene = true; Mediator.Publish(new CutsceneStartMessage()); - Mediator.Publish(new HaltScanMessage("Cutscene")); + Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene))); } else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene) { _logger.LogDebug("Cutscene end"); IsInCutscene = false; Mediator.Publish(new CutsceneEndMessage()); - Mediator.Publish(new ResumeScanMessage("Cutscene")); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene))); } if (IsInCutscene) { Mediator.Publish(new CutsceneFrameworkUpdateMessage()); return; } @@ -485,7 +502,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _logger.LogDebug("Zone switch/Gpose start"); _sentBetweenAreas = true; Mediator.Publish(new ZoneSwitchStartMessage()); - Mediator.Publish(new HaltScanMessage("Zone switch")); + Mediator.Publish(new HaltScanMessage(nameof(ConditionFlag.BetweenAreas))); } } @@ -497,10 +514,11 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _logger.LogDebug("Zone switch/Gpose end"); _sentBetweenAreas = false; Mediator.Publish(new ZoneSwitchEndMessage()); - Mediator.Publish(new ResumeScanMessage("Zone switch")); + Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); } - Mediator.Publish(new FrameworkUpdateMessage()); + if (!IsInCombat) + Mediator.Publish(new FrameworkUpdateMessage()); if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; @@ -520,6 +538,9 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new DalamudLogoutMessage()); } + if (IsInCombat) + Mediator.Publish(new FrameworkUpdateMessage()); + Mediator.Publish(new DelayedFrameworkUpdateMessage()); _delayedFrameworkUpdateCheck = DateTime.Now; diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index d5f8c02..e4a3920 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -18,7 +18,7 @@ public record OpenSettingsUiMessage : MessageBase; public record DalamudLoginMessage : MessageBase; public record DalamudLogoutMessage : MessageBase; public record FrameworkUpdateMessage : SameThreadMessage; -public record ClassJobChangedMessage(GameObjectHandler gameObjectHandler) : MessageBase; +public record ClassJobChangedMessage(GameObjectHandler GameObjectHandler) : MessageBase; public record DelayedFrameworkUpdateMessage : SameThreadMessage; public record ZoneSwitchStartMessage : MessageBase; public record ZoneSwitchEndMessage : MessageBase; @@ -78,6 +78,8 @@ public record OpenPermissionWindow(Pair Pair) : MessageBase; public record DownloadLimitChangedMessage() : SameThreadMessage; public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase; public record TargetPairMessage(Pair Pair) : MessageBase; +public record CombatStartMessage : MessageBase; +public record CombatEndMessage : MessageBase; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/Services/PerformanceCollectorService.cs b/MareSynchronos/Services/PerformanceCollectorService.cs index 8646628..5dc209d 100644 --- a/MareSynchronos/Services/PerformanceCollectorService.cs +++ b/MareSynchronos/Services/PerformanceCollectorService.cs @@ -173,7 +173,7 @@ public sealed class PerformanceCollectorService : IHostedService { try { - var last = entries.Value.Last(); + var last = entries.Value.ToList().Last(); if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now) && !_performanceCounters.TryRemove(entries.Key, out _)) { _logger.LogDebug("Could not remove performance counter {counter}", entries.Key); diff --git a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs index 936a190..de99f3f 100644 --- a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs +++ b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs @@ -65,7 +65,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase public async Task DownloadFiles(GameObjectHandler gameObject, List fileReplacementDto, CancellationToken ct) { - Mediator.Publish(new HaltScanMessage("Download")); + Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles))); try { await DownloadFilesInternal(gameObject, fileReplacementDto, ct).ConfigureAwait(false); @@ -77,7 +77,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase finally { Mediator.Publish(new DownloadFinishedMessage(gameObject)); - Mediator.Publish(new ResumeScanMessage("Download")); + Mediator.Publish(new ResumeScanMessage(nameof(DownloadFiles))); } } diff --git a/MareSynchronos/WebAPI/Files/ThrottledStream.cs b/MareSynchronos/WebAPI/Files/ThrottledStream.cs index 0e50a68..0a083f7 100644 --- a/MareSynchronos/WebAPI/Files/ThrottledStream.cs +++ b/MareSynchronos/WebAPI/Files/ThrottledStream.cs @@ -1,6 +1,4 @@ -using Microsoft.Extensions.Logging; - -namespace MareSynchronos.WebAPI.Files +namespace MareSynchronos.WebAPI.Files { /// /// Class for streaming data with throttling support. From 8a197e6387690210f987eab936a53b9bd426f151 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Thu, 7 Dec 2023 09:43:43 +0100 Subject: [PATCH 10/11] add more resilience to MCDF export and loading --- .../PlayerData/Export/MareCharaFileManager.cs | 46 +++++++++++-------- MareSynchronos/UI/GposeUi.cs | 17 +++++-- MareSynchronos/UI/SettingsUi.cs | 20 ++++---- 3 files changed, 52 insertions(+), 31 deletions(-) diff --git a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs index b5bdd82..6090831 100644 --- a/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs +++ b/MareSynchronos/PlayerData/Export/MareCharaFileManager.cs @@ -65,7 +65,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase public bool CurrentlyWorking { get; private set; } = false; public MareCharaFileHeader? LoadedCharaFile { get; private set; } - public async Task ApplyMareCharaFile(GameObject? charaTarget) + public async Task ApplyMareCharaFile(GameObject? charaTarget, long expectedLength) { if (charaTarget == null) return; Dictionary extractedFiles = new(StringComparer.Ordinal); @@ -80,8 +80,8 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression); using var reader = new BinaryReader(lz4Stream); MareCharaFileHeader.AdvanceReaderToData(reader); - _logger.LogDebug("Applying to {chara}", charaTarget.Name.TextValue); - extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader); + _logger.LogDebug("Applying to {chara}, expected length of contents: {exp}", charaTarget.Name.TextValue, expectedLength); + extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader, expectedLength); Dictionary fileSwaps = new(StringComparer.Ordinal); foreach (var fileSwap in LoadedCharaFile.CharaFileData.FileSwaps) { @@ -125,14 +125,19 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase } } } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failure to read MCDF"); + throw; + } finally { CurrentlyWorking = false; _logger.LogDebug("Clearing local files"); - foreach (var file in extractedFiles) + foreach (var file in Directory.EnumerateFiles(_configService.Current.CacheFolder, "*.tmp")) { - File.Delete(file.Value); + File.Delete(file); } } } @@ -142,7 +147,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase LoadedCharaFile = null; } - public void LoadMareCharaFile(string filePath) + public long LoadMareCharaFile(string filePath) { CurrentlyWorking = true; try @@ -179,6 +184,7 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase _logger.LogInformation("Expected length: {expected}", expectedLength); } + return expectedLength; } finally { CurrentlyWorking = false; } } @@ -214,34 +220,38 @@ public class MareCharaFileManager : DisposableMediatorSubscriberBase } } } + catch (Exception ex) + { + _logger.LogError(ex, "Failure Saving Mare Chara File, deleting output"); + File.Delete(filePath); + } finally { CurrentlyWorking = false; } } - private Dictionary ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader) + private Dictionary ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader, long expectedLength) { + long totalRead = 0; Dictionary gamePathToFilePath = new(StringComparer.Ordinal); foreach (var fileData in charaFileHeader.CharaFileData.Files) { var fileName = Path.Combine(_configService.Current.CacheFolder, "mare_" + _globalFileCounter++ + ".tmp"); - var length = fileData.Length; - var bufferSize = 4 * 1024 * 1024; + var length = (int)fileData.Length; + var bufferSize = length; using var fs = File.OpenWrite(fileName); using var wr = new BinaryWriter(fs); - int chunk = 0; - while (length > 0) - { - if (length < bufferSize) bufferSize = (int)length; - _logger.LogTrace("Reading chunk {chunk} {bufferSize}/{length} of {fileName}", chunk++, bufferSize, length, fileName); - var buffer = reader.ReadBytes(bufferSize); - wr.Write(length > bufferSize ? buffer : buffer.Take((int)length).ToArray()); - length -= bufferSize; - } + _logger.LogTrace("Reading {length} of {fileName}", length, fileName); + var buffer = reader.ReadBytes(bufferSize); + wr.Write(buffer); wr.Flush(); + wr.Close(); + if (buffer.Length == 0) throw new EndOfStreamException("Unexpected EOF"); foreach (var path in fileData.GamePaths) { gamePathToFilePath[path] = fileName; _logger.LogTrace("{path} => {fileName}", path, fileName); } + totalRead += length; + _logger.LogTrace("Read {read}/{expected} bytes", totalRead, expectedLength); } return gamePathToFilePath; diff --git a/MareSynchronos/UI/GposeUi.cs b/MareSynchronos/UI/GposeUi.cs index e90fedf..9c92b52 100644 --- a/MareSynchronos/UI/GposeUi.cs +++ b/MareSynchronos/UI/GposeUi.cs @@ -15,6 +15,8 @@ public class GposeUi : WindowMediatorSubscriberBase private readonly DalamudUtilService _dalamudUtil; private readonly FileDialogManager _fileDialogManager; private readonly MareCharaFileManager _mareCharaFileManager; + private Task? _expectedLength; + private Task? _applicationTask; public GposeUi(ILogger logger, MareCharaFileManager mareCharaFileManager, DalamudUtilService dalamudUtil, FileDialogManager fileDialogManager, MareConfigService configService, @@ -51,21 +53,28 @@ public class GposeUi : WindowMediatorSubscriberBase _configService.Current.ExportFolder = Path.GetDirectoryName(path) ?? string.Empty; _configService.Save(); - _ = Task.Run(() => _mareCharaFileManager.LoadMareCharaFile(path)); + _expectedLength = Task.Run(() => _mareCharaFileManager.LoadMareCharaFile(path)); }, 1, Directory.Exists(_configService.Current.ExportFolder) ? _configService.Current.ExportFolder : null); } UiSharedService.AttachToolTip("Applies it to the currently selected GPose actor"); - if (_mareCharaFileManager.LoadedCharaFile != null) + if (_mareCharaFileManager.LoadedCharaFile != null && _expectedLength != null) { UiSharedService.TextWrapped("Loaded file: " + _mareCharaFileManager.LoadedCharaFile.FilePath); UiSharedService.TextWrapped("File Description: " + _mareCharaFileManager.LoadedCharaFile.CharaFileData.Description); if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Check, "Apply loaded MCDF")) { - _ = Task.Run(async () => await _mareCharaFileManager.ApplyMareCharaFile(_dalamudUtil.GposeTargetGameObject).ConfigureAwait(false)); + _applicationTask = Task.Run(async () => await _mareCharaFileManager.ApplyMareCharaFile(_dalamudUtil.GposeTargetGameObject, _expectedLength!.GetAwaiter().GetResult()).ConfigureAwait(false)); } UiSharedService.AttachToolTip("Applies it to the currently selected GPose actor"); UiSharedService.ColorTextWrapped("Warning: redrawing or changing the character will revert all applied mods.", ImGuiColors.DalamudYellow); } + if (_applicationTask?.IsFaulted ?? false) + { + UiSharedService.ColorTextWrapped("Failure to read MCDF file. MCDF file is possibly corrupt. Re-export the MCDF file and try again.", + ImGuiColors.DalamudRed); + UiSharedService.ColorTextWrapped("Note: if this is your MCDF, try redrawing yourself, wait and re-export the file. " + + "If you received it from someone else have them do the same.", ImGuiColors.DalamudYellow); + } } else { @@ -77,6 +86,8 @@ public class GposeUi : WindowMediatorSubscriberBase private void EndGpose() { IsOpen = false; + _applicationTask = null; + _expectedLength = null; _mareCharaFileManager.ClearMareCharaFile(); } diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 714101e..106d90b 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -54,6 +54,7 @@ public class SettingsUi : WindowMediatorSubscriberBase private Task>? _validationTask; private CancellationTokenSource? _validationCts; private (int, int, FileCacheEntity) _currentProgress; + private Task? _exportTask; public SettingsUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, @@ -439,17 +440,11 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Current.ExportFolder = Path.GetDirectoryName(path) ?? string.Empty; _configService.Save(); - _ = Task.Run(() => + _exportTask = Task.Run(() => { - try - { - _mareCharaFileManager.SaveMareCharaFile(LastCreatedCharacterData, _exportDescription, path); - _exportDescription = string.Empty; - } - catch (Exception ex) - { - _logger.LogCritical(ex, "Error saving data"); - } + var desc = _exportDescription; + _exportDescription = string.Empty; + _mareCharaFileManager.SaveMareCharaFile(LastCreatedCharacterData, desc, path); }); }, Directory.Exists(_configService.Current.ExportFolder) ? _configService.Current.ExportFolder : null); } @@ -461,6 +456,11 @@ public class SettingsUi : WindowMediatorSubscriberBase UiSharedService.ColorTextWrapped("Export in progress", ImGuiColors.DalamudYellow); } + if (_exportTask?.IsFaulted ?? false) + { + UiSharedService.ColorTextWrapped("Export failed, check /xllog for more details.", ImGuiColors.DalamudRed); + } + ImGui.Unindent(); } bool openInGpose = _configService.Current.OpenGposeImportOnGposeStart; From 7bb0778060f844766d9989621857dad65e8d1d21 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Fri, 8 Dec 2023 01:09:24 +0100 Subject: [PATCH 11/11] fix combat situations not redrawing every time after combat ends --- .../PlayerData/Handlers/PairHandler.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index e5376e2..2421c88 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -18,6 +18,8 @@ namespace MareSynchronos.PlayerData.Handlers; public sealed class PairHandler : DisposableMediatorSubscriberBase { + private sealed record CombatData(Guid ApplicationId, CharacterData CharacterData, bool Forced); + private readonly DalamudUtilService _dalamudUtil; private readonly FileDownloadManager _downloadManager; private readonly FileCacheManager _fileDbManager; @@ -35,6 +37,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private bool _isVisible; private string _penumbraCollection; private bool _redrawOnNextApplication = false; + private CombatData? _dataReceivedInCombat; public long LastAppliedDataSize { get; private set; } public PairHandler(ILogger logger, OnlineUserIdentDto onlineUser, @@ -80,14 +83,16 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (msg) => { - if (IsVisible && _cachedData != null) + if (IsVisible && _dataReceivedInCombat != null) { - Guid g = Guid.NewGuid(); - ApplyCharacterData(g, _cachedData, true); + ApplyCharacterData(_dataReceivedInCombat.ApplicationId, + _dataReceivedInCombat.CharacterData, _dataReceivedInCombat.Forced); + _dataReceivedInCombat = null; } }); Mediator.Subscribe(this, _ => { + _dataReceivedInCombat = null; _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate(); _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); }); @@ -117,17 +122,24 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) { - if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero) || _dalamudUtil.IsInCombat) + if (_dalamudUtil.IsInCombat) { - Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, inCombat: {inCombat}, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", - applicationBase, _dalamudUtil.IsInCombat, _charaHandler == null, PlayerCharacter == IntPtr.Zero); + Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase); + _dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization); + SetUploading(isUploading: false); + return; + } + + if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) + { + Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", + applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, this, forceApplyCustomization, forceApplyMods: false) .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); _cachedData = characterData; Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods); - if (_dalamudUtil.IsInCombat) SetUploading(isUploading: false); return; } @@ -407,7 +419,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { await _ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths).ConfigureAwait(false); LastAppliedDataSize = -1; - foreach (var path in moddedPaths.Select(v => new FileInfo(v.Value)).Where(p => p.Exists)) + foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) { LastAppliedDataSize += path.Length; }