From 2221f4d09e1e66f134547a0d1d760e9fb1b39dfb Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Wed, 25 Oct 2023 16:33:03 +0200 Subject: [PATCH] fix palette application and add experimental less redraws option --- .../FileCache/TransientResourceManager.cs | 8 +++- .../Configurations/MareConfig.cs | 1 + .../PlayerData/Data/PlayerChanges.cs | 15 +++---- .../Factories/PairHandlerFactory.cs | 7 +++- .../PlayerData/Handlers/GameObjectHandler.cs | 9 ++++ .../PlayerData/Handlers/PairHandler.cs | 33 ++++++++++++--- MareSynchronos/Services/DalamudUtilService.cs | 11 ----- MareSynchronos/Services/Mediator/Messages.cs | 2 +- MareSynchronos/UI/CompactUI.cs | 4 +- MareSynchronos/UI/SettingsUi.cs | 12 ++++++ MareSynchronos/Utils/VariousExtensions.cs | 42 ++++++++++++++++++- 11 files changed, 114 insertions(+), 30 deletions(-) diff --git a/MareSynchronos/FileCache/TransientResourceManager.cs b/MareSynchronos/FileCache/TransientResourceManager.cs index 644f612..8848abd 100644 --- a/MareSynchronos/FileCache/TransientResourceManager.cs +++ b/MareSynchronos/FileCache/TransientResourceManager.cs @@ -51,7 +51,13 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase Mediator.Subscribe(this, Manager_PenumbraResourceLoadEvent); Mediator.Subscribe(this, (_) => Manager_PenumbraModSettingChanged()); Mediator.Subscribe(this, (_) => DalamudUtil_FrameworkUpdate()); - Mediator.Subscribe(this, (_) => DalamudUtil_ClassJobChanged()); + Mediator.Subscribe(this, (msg) => + { + if (_playerRelatedPointers.Contains(msg.gameObjectHandler)) + { + DalamudUtil_ClassJobChanged(); + } + }); Mediator.Subscribe(this, (msg) => { _playerRelatedPointers.Add(msg.Handler); diff --git a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs index 2c5ab9a..013c0eb 100644 --- a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs @@ -50,4 +50,5 @@ public class MareConfig : IMareConfiguration public bool UseCompactor { get; set; } = false; public int Version { get; set; } = 1; public NotificationLocation WarningNotification { get; set; } = NotificationLocation.Both; + public bool UseLessRedraws { get; set; } = false; } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Data/PlayerChanges.cs b/MareSynchronos/PlayerData/Data/PlayerChanges.cs index f33729c..bd1beb9 100644 --- a/MareSynchronos/PlayerData/Data/PlayerChanges.cs +++ b/MareSynchronos/PlayerData/Data/PlayerChanges.cs @@ -2,11 +2,12 @@ public enum PlayerChanges { - Heels = 1, - Customize = 2, - Palette = 3, - Honorific = 4, - ModFiles = 5, - ModManip = 6, - Glamourer = 7 + ModFiles = 1, + ModManip = 2, + Glamourer = 3, + Customize = 4, + Heels = 5, + Palette = 6, + Honorific = 7, + ForcedRedraw = 8, } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index f9ee5f6..73bad33 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -1,6 +1,7 @@ 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; @@ -20,12 +21,13 @@ 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) + FileCacheManager fileCacheManager, MareMediator mareMediator, MareConfigService mareConfigService) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -36,12 +38,13 @@ 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); + _fileCacheManager, _mareMediator, _mareConfigService); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 84f979f..c30a915 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -22,6 +22,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase 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, @@ -215,6 +216,14 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase if (((DrawObject*)DrawObjectAddress)->Object.GetObjectType() == ObjectType.CharacterBase && ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human) { + var classJob = chara->CharacterData.ClassJob; + if (classJob != _classJob) + { + Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob); + _classJob = classJob; + Mediator.Publish(new ClassJobChangedMessage(this)); + } + equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)DrawObjectAddress)->Head); ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand); diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 8dffb29..25f61e0 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -2,6 +2,7 @@ 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; @@ -21,6 +22,7 @@ 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; @@ -34,13 +36,15 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private bool _forceApplyMods = false; private bool _isVisible; private string _penumbraCollection; + private bool _redrawOnNextApplication = false; public PairHandler(ILogger logger, OnlineUserIdentDto onlineUser, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, FileDownloadManager transferManager, PluginWarningNotificationService pluginWarningNotificationManager, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, - FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) + FileCacheManager fileDbManager, MareMediator mediator, + MareConfigService mareConfigService) : base(logger, mediator) { OnlineUser = onlineUser; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -50,7 +54,7 @@ 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()); @@ -70,6 +74,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _charaHandler = null; } }); + Mediator.Subscribe(this, (msg) => + { + if (_mareConfigService.Current.UseLessRedraws && msg.gameObjectHandler == _charaHandler) + { + _redrawOnNextApplication = true; + } + }); } public bool IsVisible @@ -128,6 +139,12 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _forceApplyMods = false; } + if (_redrawOnNextApplication && charaDataToUpdate.TryGetValue(ObjectKind.Player, out var player)) + { + player.Add(PlayerChanges.ForcedRedraw); + _redrawOnNextApplication = false; + } + if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) { _pluginWarningNotificationManager.NotifyForMissingPlugins(OnlineUser.User, PlayerName!, playerChanges); @@ -229,6 +246,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Logger.LogDebug("[{applicationId}] Applying Customization Data for {handler}", applicationId, handler); await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, handler, applicationId, 30000, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); + if (!_mareConfigService.Current.UseLessRedraws) changes.Value.Remove(PlayerChanges.ForcedRedraw); foreach (var change in changes.Value.OrderBy(p => (int)p)) { Logger.LogDebug("[{applicationId}] Processing {change} for {handler}", applicationId, change, handler); @@ -264,15 +282,20 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase } break; - case PlayerChanges.ModFiles: - case PlayerChanges.ModManip: + case PlayerChanges.ForcedRedraw: + await _ipcManager.PenumbraRedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); + break; + + default: break; } token.ThrowIfCancellationRequested(); } - if (changes.Value.Contains(PlayerChanges.ModFiles) || changes.Value.Contains(PlayerChanges.ModManip) || changes.Value.Contains(PlayerChanges.Glamourer)) + if (!_mareConfigService.Current.UseLessRedraws && (changes.Value.Contains(PlayerChanges.ModFiles) || changes.Value.Contains(PlayerChanges.ModManip) || changes.Value.Contains(PlayerChanges.Glamourer))) + { await _ipcManager.PenumbraRedrawAsync(Logger, handler, applicationId, token).ConfigureAwait(false); + } } finally { diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 9a662ed..724cfad 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -494,17 +494,6 @@ public class DalamudUtilService : IHostedService _mediator.Publish(new DalamudLogoutMessage()); } - if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid()) - { - var newclassJobId = _clientState.LocalPlayer.ClassJob.Id; - - if (_classJobId != newclassJobId) - { - _classJobId = newclassJobId; - _mediator.Publish(new ClassJobChangedMessage(_classJobId)); - } - } - _mediator.Publish(new DelayedFrameworkUpdateMessage()); _delayedFrameworkUpdateCheck = DateTime.Now; diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 4d7b802..c814c26 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(uint? ClassJob) : MessageBase; +public record ClassJobChangedMessage(GameObjectHandler gameObjectHandler) : MessageBase; public record DelayedFrameworkUpdateMessage : SameThreadMessage; public record ZoneSwitchStartMessage : MessageBase; public record ZoneSwitchEndMessage : MessageBase; diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 4e8f0cb..cd0a177 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -183,8 +183,8 @@ public class CompactUi : WindowMediatorSubscriberBase { var ySize = TransferPartHeight == 0 ? 1 - : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y - + ImGui.GetTextLineHeight() - ImGui.GetStyle().WindowBorderSize) - TransferPartHeight - ImGui.GetCursorPosY(); + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y + + ImGui.GetTextLineHeight() - ImGui.GetStyle().WindowPadding.Y - ImGui.GetStyle().WindowBorderSize) - TransferPartHeight - ImGui.GetCursorPosY(); ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false); diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 3f68028..6dc32b0 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -515,6 +515,18 @@ public class SettingsUi : WindowMediatorSubscriberBase } _lastTab = "General"; + UiSharedService.FontText("General Settings", _uiShared.UidFont); + bool lessRedraws = _configService.Current.UseLessRedraws; + if (ImGui.Checkbox("[Experimental] Use less redraws", ref lessRedraws)) + { + _configService.Current.UseLessRedraws = lessRedraws; + _configService.Save(); + } + UiSharedService.DrawHelpText("This will attempt to use less redraws. Changes that solely affect the players body appearance (i.e. clothes) should not cause a redraw anymore." + Environment.NewLine + + "Some changes, especially to hair, face, tail or any vfx, animation or skeleton changes, or class changes will still force a redraw." + Environment.NewLine + Environment.NewLine + + "WARNING: this is an experimental, little tested feature and can potentially lead to issues with animation state or crashes. Use at your own risk."); + ImGui.Separator(); + UiSharedService.FontText("Notes", _uiShared.UidFont); if (UiSharedService.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard")) { diff --git a/MareSynchronos/Utils/VariousExtensions.cs b/MareSynchronos/Utils/VariousExtensions.cs index 4b66828..9e66477 100644 --- a/MareSynchronos/Utils/VariousExtensions.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -59,6 +59,7 @@ public static class VariousExtensions cachedPlayer, objectKind, hasNewButNotOldFileReplacements, hasOldButNotNewFileReplacements, hasNewButNotOldGlamourerData, hasOldButNotNewGlamourerData, PlayerChanges.ModFiles, PlayerChanges.Glamourer); charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); charaDataToUpdate[objectKind].Add(PlayerChanges.Glamourer); + charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw); } else { @@ -69,6 +70,43 @@ public static class VariousExtensions { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (FileReplacements not equal) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModFiles); charaDataToUpdate[objectKind].Add(PlayerChanges.ModFiles); + if (forceApplyMods || objectKind != ObjectKind.Player) + { + charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw); + } + else + { + var existingFace = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var existingHair = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var existingTail = existingFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var newFace = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/face/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var newHair = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/hair/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var newTail = newFileReplacements.Where(g => g.GamePaths.Any(p => p.Contains("/tail/", StringComparison.OrdinalIgnoreCase))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var existingTransients = existingFileReplacements.Where(g => g.GamePaths.Any(g => !g.EndsWith("mdl") && !g.EndsWith("tex") && !g.EndsWith("mtrl"))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + var newTransients = newFileReplacements.Where(g => g.GamePaths.Any(g => !g.EndsWith("mdl") && !g.EndsWith("tex") && !g.EndsWith("mtrl"))) + .OrderBy(g => string.IsNullOrEmpty(g.Hash) ? g.FileSwapPath : g.Hash, StringComparer.OrdinalIgnoreCase).ToList(); + + logger.LogTrace("[BASE-{appbase}] ExistingFace: {of}, NewFace: {fc}; ExistingHair: {eh}, NewHair: {nh}; ExistingTail: {et}, NewTail: {nt}; ExistingTransient: {etr}, NewTransient: {ntr}", applicationBase, + existingFace.Count, newFace.Count, existingHair.Count, newHair.Count, existingTail.Count, newTail.Count, existingTransients.Count, newTransients.Count); + + var differentFace = !existingFace.SequenceEqual(newFace, PlayerData.Data.FileReplacementDataComparer.Instance); + var differentHair = !existingHair.SequenceEqual(newHair, PlayerData.Data.FileReplacementDataComparer.Instance); + var differentTail = !existingTail.SequenceEqual(newTail, PlayerData.Data.FileReplacementDataComparer.Instance); + var differenTransients = !existingTransients.SequenceEqual(newTransients, PlayerData.Data.FileReplacementDataComparer.Instance); + if (differentFace || differentHair || differentTail || differenTransients) + { + logger.LogDebug("[BASE-{appbase}] Different Subparts: Face: {face}, Hair: {hair}, Tail: {tail}, Transients: {transients} => {change}", applicationBase, + differentFace, differentHair, differentTail, differenTransients, PlayerChanges.ForcedRedraw); + charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw); + } + } } } @@ -100,6 +138,7 @@ public static class VariousExtensions { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff manip data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.ModManip); charaDataToUpdate[objectKind].Add(PlayerChanges.ModManip); + charaDataToUpdate[objectKind].Add(PlayerChanges.ForcedRedraw); } bool heelsOffsetDifferent = !string.Equals(oldData.HeelsData, newData.HeelsData, StringComparison.Ordinal); @@ -109,7 +148,8 @@ public static class VariousExtensions charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); } - bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); + bool palettePlusDataDifferent = !string.Equals(oldData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal) + || (charaDataToUpdate.TryGetValue(objectKind, out var playerChanges) && playerChanges.Contains(PlayerChanges.Glamourer)); if (palettePlusDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.PalettePlusData))) { logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff palette data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Palette);