From deb76f42a4a08ba83e6a845214af481c0a270ce2 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 13 Aug 2022 21:01:22 +0200 Subject: [PATCH 01/28] update to net6 --- MareSynchronos/MareSynchronos.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 9068840..e0d8e07 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -10,7 +10,7 @@ - net5.0-windows7.0 + net6.0-windows x64 enable latest @@ -28,8 +28,8 @@ - - + + From 4226f2e16d7aa67c2bd3342aa3c36b747edad518 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 13 Aug 2022 21:16:05 +0200 Subject: [PATCH 02/28] add penumbra ipc for pathchanges, change all tolower to tolowerinvariant --- MareSynchronos/Managers/FileCacheManager.cs | 8 ++++---- MareSynchronos/Managers/IpcManager.cs | 8 ++++++++ MareSynchronos/Models/FileReplacement.cs | 10 +++++----- MareSynchronos/UI/CompactUI.cs | 4 ++-- MareSynchronos/UI/UIShared.cs | 2 +- MareSynchronos/WebAPI/ApIController.Functions.Files.cs | 2 +- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index b1d75f4..14946b8 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -64,7 +64,7 @@ namespace MareSynchronos.Managers var sha1Hash = Crypto.GetFileHash(fileInfo.FullName); return new FileCache() { - Filepath = fileInfo.FullName.ToLower(), + Filepath = fileInfo.FullName.ToLowerInvariant(), Hash = sha1Hash, LastModifiedDate = fileInfo.LastWriteTimeUtc.Ticks.ToString(), }; @@ -203,7 +203,7 @@ namespace MareSynchronos.Managers { PluginLog.Verbose("Removed: " + item); - db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == item.ToLower())); + db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLowerInvariant() == item.ToLowerInvariant())); } else { @@ -211,7 +211,7 @@ namespace MareSynchronos.Managers var fileCache = Create(item, _rescanTaskCancellationTokenSource.Token); if (fileCache != null) { - db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == fileCache.Filepath.ToLower())); + db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLowerInvariant() == fileCache.Filepath.ToLowerInvariant())); await db.AddAsync(fileCache, _rescanTaskCancellationTokenSource.Token); } } @@ -316,7 +316,7 @@ namespace MareSynchronos.Managers foreach (var deletion in fileCachesToDelete) { var entries = db.FileCaches.Where(f => - f.Hash == deletion.Hash && f.Filepath.ToLower() == deletion.Filepath.ToLower()); + f.Hash == deletion.Hash && f.Filepath.ToLowerInvariant() == deletion.Filepath.ToLowerInvariant()); if (await entries.AnyAsync(ct)) { Logger.Verbose("Removing file from DB: " + deletion.Filepath); diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 7aa4a75..5ac24e0 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -31,6 +31,7 @@ namespace MareSynchronos.Managers private readonly ICallGateSubscriber? _reverseResolvePlayer; private readonly ICallGateSubscriber, string, int, int> _penumbraSetTemporaryMod; + private readonly ICallGateSubscriber _penumbraPlayerPathResolved; private readonly DalamudUtil _dalamudUtil; public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) @@ -55,7 +56,9 @@ namespace MareSynchronos.Managers _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); + _penumbraPlayerPathResolved = pi.GetIpcSubscriber("Penumbra.PlayerFileResourceResolved"); + _penumbraPlayerPathResolved.Subscribe(PlayerPathResolved); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraInit.Subscribe(PenumbraInit); _penumbraDispose.Subscribe(PenumbraDispose); @@ -78,6 +81,11 @@ namespace MareSynchronos.Managers this._dalamudUtil = dalamudUtil; } + private void PlayerPathResolved(string arg1, string arg2) + { + Logger.Debug($"Resolved {arg1} => {arg2}"); + } + public event VoidDelegate? PenumbraInitialized; public event VoidDelegate? PenumbraDisposed; public event PenumbraRedrawEvent? PenumbraRedrawEvent; diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index a9b696b..5b48206 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -32,7 +32,7 @@ namespace MareSynchronos.Models public void SetResolvedPath(string path) { - ResolvedPath = path.ToLower().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/'); + ResolvedPath = path.ToLowerInvariant().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/'); if (!HasFileReplacement) return; _ = Task.Run(() => @@ -40,7 +40,7 @@ namespace MareSynchronos.Models FileCache? fileCache; using (FileCacheContext db = new()) { - fileCache = db.FileCaches.FirstOrDefault(f => f.Filepath == path.ToLower()); + fileCache = db.FileCaches.FirstOrDefault(f => f.Filepath == path.ToLowerInvariant()); } if (fileCache != null) @@ -54,7 +54,7 @@ namespace MareSynchronos.Models { Hash = ComputeHash(fi); using var db = new FileCacheContext(); - var newTempCache = db.FileCaches.Single(f => f.Filepath == path.ToLower()); + var newTempCache = db.FileCaches.Single(f => f.Filepath == path.ToLowerInvariant()); newTempCache.Hash = Hash; db.Update(newTempCache); db.SaveChanges(); @@ -88,7 +88,7 @@ namespace MareSynchronos.Models string hash = Crypto.GetFileHash(fi.FullName); using FileCacheContext db = new(); - var fileAddedDuringCompute = db.FileCaches.FirstOrDefault(f => f.Filepath == fi.FullName.ToLower()); + var fileAddedDuringCompute = db.FileCaches.FirstOrDefault(f => f.Filepath == fi.FullName.ToLowerInvariant()); if (fileAddedDuringCompute != null) return fileAddedDuringCompute.Hash; try @@ -96,7 +96,7 @@ namespace MareSynchronos.Models db.Add(new FileCache() { Hash = hash, - Filepath = fi.FullName.ToLower(), + Filepath = fi.FullName.ToLowerInvariant(), LastModifiedDate = fi.LastWriteTimeUtc.Ticks.ToString() }); db.SaveChanges(); diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 3097f10..b48d416 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -273,8 +273,8 @@ namespace MareSynchronos.UI { if (_characterOrCommentFilter.IsNullOrEmpty()) return true; _configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment); - return p.OtherUID.ToLower().Contains(_characterOrCommentFilter.ToLower()) || - (comment?.ToLower().Contains(_characterOrCommentFilter.ToLower()) ?? false); + return p.OtherUID.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) || + (comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false); }); if (_configuration.ReverseUserSort) users = users.Reverse(); diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index d4ff9c5..277084e 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -424,7 +424,7 @@ namespace MareSynchronos.UI { if (!success) return; - _isPenumbraDirectory = path.ToLower() == _ipcManager.PenumbraModDirectory()?.ToLower(); + _isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(); _isDirectoryWritable = IsDirectoryWritable(path); _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); _cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript); diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index a52459d..6fc4a86 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -172,7 +172,7 @@ namespace MareSynchronos.WebAPI { CurrentUploads.Add(new UploadFileTransfer(file) { - Total = new FileInfo(db.FileCaches.FirstOrDefault(f => f.Hash.ToLower() == file.Hash.ToLower()) + Total = new FileInfo(db.FileCaches.FirstOrDefault(f => f.Hash.ToLowerInvariant() == file.Hash.ToLowerInvariant()) ?.Filepath ?? string.Empty).Length }); } From 8459fe8f256bc9bea054afae4fe4504ab02df466 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 15 Aug 2022 17:36:43 +0200 Subject: [PATCH 03/28] add some preliminary vfx work --- .../Factories/CharacterDataFactory.cs | 65 +++++++++++++- MareSynchronos/Managers/FileCacheManager.cs | 4 +- MareSynchronos/Managers/IpcManager.cs | 17 ++-- MareSynchronos/Managers/PlayerManager.cs | 30 +++++-- .../Managers/TransientResourceManager.cs | 87 +++++++++++++++++++ MareSynchronos/Models/FileReplacement.cs | 5 +- MareSynchronos/Models/PlayerRelatedObject.cs | 2 +- MareSynchronos/Plugin.cs | 9 +- MareSynchronos/Utils/DalamudUtil.cs | 13 +++ .../WebAPI/ApIController.Functions.Files.cs | 2 +- .../WebAPI/ApiController.Connectivity.cs | 9 +- 11 files changed, 217 insertions(+), 26 deletions(-) create mode 100644 MareSynchronos/Managers/TransientResourceManager.cs diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 58547ed..b9aa74e 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -22,13 +22,15 @@ public class CharacterDataFactory { private readonly DalamudUtil _dalamudUtil; private readonly IpcManager _ipcManager; + private readonly TransientResourceManager transientResourceManager; - public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager) + public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager) { Logger.Verbose("Creating " + nameof(CharacterDataFactory)); _dalamudUtil = dalamudUtil; _ipcManager = ipcManager; + this.transientResourceManager = transientResourceManager; } public CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token) @@ -167,6 +169,26 @@ public class CharacterDataFactory } } + private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0) + { + if (varPath.IsNullOrEmpty()) return; + + //Logger.Verbose("Adding File Replacement for Texture " + texPath); + + if (cache.FileReplacements.ContainsKey(objectKind)) + { + if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath))) + { + return; + } + } + + var variousReplacement = CreateFileReplacement(varPath, false); + DebugPrint(variousReplacement, objectKind, "Various", inheritanceLevel); + + cache.AddFileReplacement(objectKind, variousReplacement); + } + private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true) { if (texPath.IsNullOrEmpty()) return; @@ -233,6 +255,11 @@ public class CharacterDataFactory AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0); } + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource((IntPtr)human, item); + } + if (objectKind == ObjectKind.Player) { var weaponObject = (Weapon*)((Object*)human)->ChildObject; @@ -243,11 +270,33 @@ public class CharacterDataFactory AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0); + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource((IntPtr)weaponObject, item); + } + + foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) + { + Logger.Verbose("Found transient weapon resource: " + item); + AddReplacementsFromTexture(item, objectKind, previousData, 0, false); + } + if (weaponObject->NextSibling != (IntPtr)weaponObject) { var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel; AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1); + + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item); + } + + foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) + { + Logger.Verbose("Found transient offhand weapon resource: " + item); + AddReplacement(item, objectKind, previousData, 1); + } } } @@ -268,11 +317,21 @@ public class CharacterDataFactory { Logger.Warn("Could not get Legacy Body Decal Data"); } + + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource((IntPtr)human, item); + } + } + + foreach (var item in transientResourceManager.GetTransientResources((IntPtr)human)) + { + Logger.Verbose("Found transient resource: " + item); + AddReplacement(item, objectKind, previousData, 1); } st.Stop(); Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); - return previousData; } @@ -282,8 +341,6 @@ public class CharacterDataFactory string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb"; - //Logger.Verbose("Adding File Replacement for Skeleton " + skeletonPath); - var replacement = CreateFileReplacement(skeletonPath, true); cache.AddFileReplacement(objectKind, replacement); diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index 14946b8..5538a25 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -203,7 +203,7 @@ namespace MareSynchronos.Managers { PluginLog.Verbose("Removed: " + item); - db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLowerInvariant() == item.ToLowerInvariant())); + db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == item.ToLowerInvariant())); } else { @@ -211,7 +211,7 @@ namespace MareSynchronos.Managers var fileCache = Create(item, _rescanTaskCancellationTokenSource.Token); if (fileCache != null) { - db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLowerInvariant() == fileCache.Filepath.ToLowerInvariant())); + db.RemoveRange(db.FileCaches.Where(f => f.Filepath.ToLower() == fileCache.Filepath.ToLowerInvariant())); await db.AddAsync(fileCache, _rescanTaskCancellationTokenSource.Token); } } diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 5ac24e0..855a3e3 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -9,6 +9,7 @@ using MareSynchronos.WebAPI; namespace MareSynchronos.Managers { public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx); + public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath); public class IpcManager : IDisposable { private readonly ICallGateSubscriber _glamourerApiVersion; @@ -31,7 +32,7 @@ namespace MareSynchronos.Managers private readonly ICallGateSubscriber? _reverseResolvePlayer; private readonly ICallGateSubscriber, string, int, int> _penumbraSetTemporaryMod; - private readonly ICallGateSubscriber _penumbraPlayerPathResolved; + private readonly ICallGateSubscriber _penumbraResourceLoaded; private readonly DalamudUtil _dalamudUtil; public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) @@ -56,9 +57,9 @@ namespace MareSynchronos.Managers _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); - _penumbraPlayerPathResolved = pi.GetIpcSubscriber("Penumbra.PlayerFileResourceResolved"); + _penumbraResourceLoaded = pi.GetIpcSubscriber("Penumbra.ResourceLoaded"); - _penumbraPlayerPathResolved.Subscribe(PlayerPathResolved); + _penumbraResourceLoaded.Subscribe(ResourceLoaded); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraInit.Subscribe(PenumbraInit); _penumbraDispose.Subscribe(PenumbraDispose); @@ -81,14 +82,19 @@ namespace MareSynchronos.Managers this._dalamudUtil = dalamudUtil; } - private void PlayerPathResolved(string arg1, string arg2) + private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) { - Logger.Debug($"Resolved {arg1} => {arg2}"); + if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0) + { + PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2); + //Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}"); + } } public event VoidDelegate? PenumbraInitialized; public event VoidDelegate? PenumbraDisposed; public event PenumbraRedrawEvent? PenumbraRedrawEvent; + public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent; public bool Initialized => CheckPenumbraApi(); public bool CheckGlamourerApi() @@ -122,6 +128,7 @@ namespace MareSynchronos.Managers _penumbraDispose.Unsubscribe(PenumbraDispose); _penumbraInit.Unsubscribe(PenumbraInit); _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); + _penumbraResourceLoaded.Unsubscribe(ResourceLoaded); } public void GlamourerApplyAll(string? customization, IntPtr obj) diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index b925018..5c6638a 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -21,6 +21,7 @@ namespace MareSynchronos.Managers private readonly ApiController _apiController; private readonly CharacterDataFactory _characterDataFactory; private readonly DalamudUtil _dalamudUtil; + private readonly TransientResourceManager _transientResourceManager; private readonly IpcManager _ipcManager; public event PlayerHasChanged? PlayerHasChanged; public CharacterCacheDto? LastCreatedCharacterData { get; private set; } @@ -34,7 +35,7 @@ namespace MareSynchronos.Managers private List playerRelatedObjects = new List(); public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, - CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil) + CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager) { Logger.Verbose("Creating " + nameof(PlayerManager)); @@ -42,10 +43,11 @@ namespace MareSynchronos.Managers _ipcManager = ipcManager; _characterDataFactory = characterDataFactory; _dalamudUtil = dalamudUtil; - + _transientResourceManager = transientResourceManager; _apiController.Connected += ApiControllerOnConnected; _apiController.Disconnected += ApiController_Disconnected; _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; + _transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad; Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected); if (_apiController.IsConnected) @@ -63,6 +65,19 @@ namespace MareSynchronos.Managers }; } + public void HandleTransientResourceLoad(IntPtr drawObj) + { + foreach (var obj in playerRelatedObjects) + { + if (obj.DrawObjectAddress == drawObj && !obj.HasUnprocessedUpdate) + { + obj.HasUnprocessedUpdate = true; + OnPlayerOrAttachedObjectsChanged(); + return; + } + } + } + public void Dispose() { Logger.Verbose("Disposing " + nameof(PlayerManager)); @@ -72,13 +87,17 @@ namespace MareSynchronos.Managers _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; _dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate; + + _transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad; + + _playerChangedCts?.Cancel(); } private unsafe void DalamudUtilOnFrameworkUpdate() { - if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; + //if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; - if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return; + //if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return; playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject()); if (playerRelatedObjects.Any(c => c.HasUnprocessedUpdate && !c.IsProcessing)) @@ -86,7 +105,7 @@ namespace MareSynchronos.Managers OnPlayerOrAttachedObjectsChanged(); } - _lastPlayerObjectCheck = DateTime.Now; + //_lastPlayerObjectCheck = DateTime.Now; } private void ApiControllerOnConnected() @@ -119,6 +138,7 @@ namespace MareSynchronos.Managers while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) { + Logger.Verbose("Waiting until cache is ready"); await Task.Delay(50, token); } diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs new file mode 100644 index 0000000..dcd52a0 --- /dev/null +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -0,0 +1,87 @@ +using MareSynchronos.Models; +using MareSynchronos.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MareSynchronos.Managers +{ + public delegate void TransientResourceLoadedEvent(IntPtr drawObject); + + public class TransientResourceManager : IDisposable + { + private readonly IpcManager manager; + private readonly DalamudUtil dalamudUtil; + public event TransientResourceLoadedEvent? TransientResourceLoaded; + + private Dictionary> TransientResources { get; } = new(); + public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil) + { + manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent; + this.manager = manager; + this.dalamudUtil = dalamudUtil; + dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate; + } + + private void DalamudUtil_FrameworkUpdate() + { + foreach (var item in TransientResources.ToList()) + { + if (!dalamudUtil.IsDrawObjectPresent(item.Key)) + { + Logger.Debug("Object not present anymore: " + item.Key); + TransientResources.Remove(item.Key); + } + } + } + + public List GetTransientResources(IntPtr drawObject) + { + if (TransientResources.TryGetValue(drawObject, out var result)) + { + return result.ToList(); + } + + return new List(); + } + + private void Manager_PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath) + { + if (!TransientResources.ContainsKey(drawObject)) + { + TransientResources[drawObject] = new(); + } + + if (filePath.StartsWith("|")) + { + filePath = filePath.Split("|")[2]; + } + + var newPath = filePath.ToLowerInvariant().Replace("\\", "/"); + + if (filePath != gamePath && !TransientResources[drawObject].Contains(newPath)) + { + TransientResources[drawObject].Add(newPath); + Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {drawObject}"); + TransientResourceLoaded?.Invoke(drawObject); + } + } + + public void RemoveTransientResource(IntPtr drawObject, FileReplacement fileReplacement) + { + if (TransientResources.ContainsKey(drawObject)) + { + TransientResources[drawObject].RemoveWhere(f => fileReplacement.ResolvedPath == f); + } + } + + public void Dispose() + { + dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate; + manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent; + TransientResources.Clear(); + } + } +} diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index 5b48206..73260c5 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -27,9 +27,9 @@ namespace MareSynchronos.Models public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath); public string Hash { get; set; } = string.Empty; - + public string ResolvedPath { get; set; } = string.Empty; - + public void SetResolvedPath(string path) { ResolvedPath = path.ToLowerInvariant().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/'); @@ -93,6 +93,7 @@ namespace MareSynchronos.Models try { + Logger.Debug("Adding new file to DB: " + fi.FullName + ", " + hash); db.Add(new FileCache() { Hash = hash, diff --git a/MareSynchronos/Models/PlayerRelatedObject.cs b/MareSynchronos/Models/PlayerRelatedObject.cs index 08b4393..1c8fde9 100644 --- a/MareSynchronos/Models/PlayerRelatedObject.cs +++ b/MareSynchronos/Models/PlayerRelatedObject.cs @@ -53,7 +53,7 @@ namespace MareSynchronos.Models if (addr || equip || drawObj || nameChange) { _name = name; - Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + curPtr + ", " + (IntPtr)chara->GameObject.DrawObject); + Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}"); Address = curPtr; DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 1502066..d62b9e0 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -30,6 +30,7 @@ namespace MareSynchronos private readonly SettingsUi _settingsUi; private readonly WindowSystem _windowSystem; private PlayerManager? _playerManager; + private TransientResourceManager? _transientResourceManager; private readonly DalamudUtil _dalamudUtil; private OnlinePlayerManager? _characterCacheManager; private readonly DownloadUi _downloadUi; @@ -129,6 +130,7 @@ namespace MareSynchronos _ipcManager?.Dispose(); _playerManager?.Dispose(); _characterCacheManager?.Dispose(); + _transientResourceManager?.Dispose(); Logger.Debug("Shut down"); } @@ -160,6 +162,7 @@ namespace MareSynchronos Logger.Debug("Client logout"); _characterCacheManager?.Dispose(); _playerManager?.Dispose(); + _transientResourceManager?.Dispose(); PluginInterface.UiBuilder.Draw -= Draw; PluginInterface.UiBuilder.OpenConfigUi -= OpenUi; _commandManager.RemoveHandler(CommandName); @@ -169,6 +172,7 @@ namespace MareSynchronos { _characterCacheManager?.Dispose(); _playerManager?.Dispose(); + _transientResourceManager?.Dispose(); Task.Run(WaitForPlayerAndLaunchCharacterManager); } @@ -182,10 +186,11 @@ namespace MareSynchronos try { + _transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil); var characterCacheFactory = - new CharacterDataFactory(_dalamudUtil, _ipcManager); + new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager); _playerManager = new PlayerManager(_apiController, _ipcManager, - characterCacheFactory, _dalamudUtil); + characterCacheFactory, _dalamudUtil, _transientResourceManager); _characterCacheManager = new OnlinePlayerManager(_framework, _apiController, _dalamudUtil, _ipcManager, _playerManager); } diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 4588bc5..4e70830 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -28,6 +28,19 @@ namespace MareSynchronos.Utils public event LogOut? LogOut; public event FrameworkUpdate? FrameworkUpdate; + public unsafe bool IsDrawObjectPresent(IntPtr key) + { + foreach (var obj in _objectTable) + { + if ((IntPtr)((GameObject*)obj.Address)->GetDrawObject() == key) + { + return true; + } + } + + return false; + } + public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework) { _clientState = clientState; diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 6fc4a86..a52459d 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -172,7 +172,7 @@ namespace MareSynchronos.WebAPI { CurrentUploads.Add(new UploadFileTransfer(file) { - Total = new FileInfo(db.FileCaches.FirstOrDefault(f => f.Hash.ToLowerInvariant() == file.Hash.ToLowerInvariant()) + Total = new FileInfo(db.FileCaches.FirstOrDefault(f => f.Hash.ToLower() == file.Hash.ToLower()) ?.Filepath ?? string.Empty).Length }); } diff --git a/MareSynchronos/WebAPI/ApiController.Connectivity.cs b/MareSynchronos/WebAPI/ApiController.Connectivity.cs index 4df15ba..802cff4 100644 --- a/MareSynchronos/WebAPI/ApiController.Connectivity.cs +++ b/MareSynchronos/WebAPI/ApiController.Connectivity.cs @@ -128,6 +128,10 @@ namespace MareSynchronos.WebAPI public async Task CreateConnections() { + Logger.Info("Recreating Connection"); + + await StopConnection(_connectionCancellationTokenSource.Token); + if (_pluginConfiguration.FullPause) { ServerState = ServerState.Disconnected; @@ -135,10 +139,6 @@ namespace MareSynchronos.WebAPI return; } - Logger.Info("Recreating Connection"); - - await StopConnection(_connectionCancellationTokenSource.Token); - _connectionCancellationTokenSource.Cancel(); _connectionCancellationTokenSource = new CancellationTokenSource(); var token = _connectionCancellationTokenSource.Token; @@ -329,6 +329,7 @@ namespace MareSynchronos.WebAPI { if (_mareHub is not null) { + _uploadCancellationTokenSource?.Cancel(); Logger.Info("Stopping existing connection"); await _mareHub.StopAsync(token); _mareHub.Closed -= MareHubOnClosed; From e5e747029ec28b4e56da775c37284bfcd16571b8 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 19 Aug 2022 11:33:56 +0200 Subject: [PATCH 04/28] update on net6 --- MareAPI | 2 +- MareSynchronos/WebAPI/ApIController.Functions.Files.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/MareAPI b/MareAPI index 21fe024..374cc31 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 21fe024dfb8cecad466dfc8ceef15040300ad342 +Subproject commit 374cc31babec9ac37aa21104d7fc02e41cd0d38e diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 2d7809f..100f54a 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -74,10 +74,7 @@ namespace MareSynchronos.WebAPI Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")"); List downloadFileInfoFromService = new List(); - foreach (var file in fileReplacementDto) - { - downloadFileInfoFromService.Add(await _mareHub!.InvokeAsync(Api.InvokeFileGetFileSize, file.Hash, ct)); - } + downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(m => m.Hash).ToList(), ct)); CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) .Where(d => d.CanBeTransferred).ToList(); From 514ae1f1411d1ace52769b6a6db819f9372475ed Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 15:05:19 +0200 Subject: [PATCH 05/28] adjust api --- MareAPI | 2 +- MareSynchronos/Managers/IpcManager.cs | 10 +++++----- Penumbra | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MareAPI b/MareAPI index 374cc31..645dd0b 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 374cc31babec9ac37aa21104d7fc02e41cd0d38e +Subproject commit 645dd0b18f815cb7ccd81f37e84ded9cf650c3f3 diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 855a3e3..9a40397 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -32,7 +32,7 @@ namespace MareSynchronos.Managers private readonly ICallGateSubscriber? _reverseResolvePlayer; private readonly ICallGateSubscriber, string, int, int> _penumbraSetTemporaryMod; - private readonly ICallGateSubscriber _penumbraResourceLoaded; + private readonly ICallGateSubscriber _penumbraGameObjectResourcePathResolved; private readonly DalamudUtil _dalamudUtil; public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) @@ -57,9 +57,9 @@ namespace MareSynchronos.Managers _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); - _penumbraResourceLoaded = pi.GetIpcSubscriber("Penumbra.ResourceLoaded"); + _penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber("Penumbra.GameObjectResourcePathResolved"); - _penumbraResourceLoaded.Subscribe(ResourceLoaded); + _penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded); _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); _penumbraInit.Subscribe(PenumbraInit); _penumbraDispose.Subscribe(PenumbraDispose); @@ -113,7 +113,7 @@ namespace MareSynchronos.Managers { try { - return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 11 }; + return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 }; } catch { @@ -128,7 +128,7 @@ namespace MareSynchronos.Managers _penumbraDispose.Unsubscribe(PenumbraDispose); _penumbraInit.Unsubscribe(PenumbraInit); _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); - _penumbraResourceLoaded.Unsubscribe(ResourceLoaded); + _penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded); } public void GlamourerApplyAll(string? customization, IntPtr obj) diff --git a/Penumbra b/Penumbra index cfc441b..e66ca7c 160000 --- a/Penumbra +++ b/Penumbra @@ -1 +1 @@ -Subproject commit cfc441b9b1acaaf1bf2da758bd875cfb5229ab60 +Subproject commit e66ca7c5805bf23a82b51649e7f5f75baabe4bc7 From 8d6b7346ecec015a6bf1c4f7b757146015bbbece Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 17:42:59 +0200 Subject: [PATCH 06/28] adjustments for animation/penumbra update --- .../Factories/CharacterDataFactory.cs | 8 ++++---- MareSynchronos/Managers/CachedPlayer.cs | 5 ++--- MareSynchronos/Managers/FileCacheManager.cs | 2 +- MareSynchronos/Managers/PlayerManager.cs | 2 +- .../Managers/TransientResourceManager.cs | 20 +++++++++---------- MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/Models/PlayerRelatedObject.cs | 13 +++++++++++- MareSynchronos/Utils/DalamudUtil.cs | 6 +++--- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 3471e8a..f5b1a08 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -266,7 +266,7 @@ public class CharacterDataFactory foreach (var item in previousData.FileReplacements[objectKind]) { - transientResourceManager.RemoveTransientResource((IntPtr)human, item); + transientResourceManager.RemoveTransientResource(charaPointer, item); } if (objectKind == ObjectKind.Player) @@ -281,7 +281,7 @@ public class CharacterDataFactory foreach (var item in previousData.FileReplacements[objectKind]) { - transientResourceManager.RemoveTransientResource((IntPtr)weaponObject, item); + transientResourceManager.RemoveTransientResource(charaPointer, item); } foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) @@ -329,11 +329,11 @@ public class CharacterDataFactory foreach (var item in previousData.FileReplacements[objectKind]) { - transientResourceManager.RemoveTransientResource((IntPtr)human, item); + transientResourceManager.RemoveTransientResource(charaPointer, item); } } - foreach (var item in transientResourceManager.GetTransientResources((IntPtr)human)) + foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) { Logger.Verbose("Found transient resource: " + item); AddReplacement(item, objectKind, previousData, 1); diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 5070de0..2845e46 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -348,6 +348,8 @@ public class CachedPlayer _dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate; _ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent; _ipcManager.PenumbraRemoveTemporaryCollection(PlayerName); + _downloadCancellationTokenSource?.Cancel(); + _downloadCancellationTokenSource?.Dispose(); if (PlayerCharacter != null && PlayerCharacter.IsValid()) { foreach (var item in _cachedData.FileReplacements) @@ -355,9 +357,6 @@ public class CachedPlayer RevertCustomizationData(item.Key); } } - - _downloadCancellationTokenSource?.Cancel(); - _downloadCancellationTokenSource?.Dispose(); } catch (Exception ex) { diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index 5538a25..d415e3c 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -316,7 +316,7 @@ namespace MareSynchronos.Managers foreach (var deletion in fileCachesToDelete) { var entries = db.FileCaches.Where(f => - f.Hash == deletion.Hash && f.Filepath.ToLowerInvariant() == deletion.Filepath.ToLowerInvariant()); + f.Hash == deletion.Hash && f.Filepath.ToLower() == deletion.Filepath.ToLower()); if (await entries.AnyAsync(ct)) { Logger.Verbose("Removing file from DB: " + deletion.Filepath); diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index baef948..1616997 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -65,7 +65,7 @@ namespace MareSynchronos.Managers { foreach (var obj in playerRelatedObjects) { - if (obj.DrawObjectAddress == drawObj && !obj.HasUnprocessedUpdate) + if (obj.Address == drawObj && !obj.HasUnprocessedUpdate) { obj.HasUnprocessedUpdate = true; OnPlayerOrAttachedObjectsChanged(); diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index dcd52a0..857740a 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -29,7 +29,7 @@ namespace MareSynchronos.Managers { foreach (var item in TransientResources.ToList()) { - if (!dalamudUtil.IsDrawObjectPresent(item.Key)) + if (!dalamudUtil.IsGameObjectPresent(item.Key)) { Logger.Debug("Object not present anymore: " + item.Key); TransientResources.Remove(item.Key); @@ -37,9 +37,9 @@ namespace MareSynchronos.Managers } } - public List GetTransientResources(IntPtr drawObject) + public List GetTransientResources(IntPtr gameObject) { - if (TransientResources.TryGetValue(drawObject, out var result)) + if (TransientResources.TryGetValue(gameObject, out var result)) { return result.ToList(); } @@ -47,11 +47,11 @@ namespace MareSynchronos.Managers return new List(); } - private void Manager_PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath) + private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) { - if (!TransientResources.ContainsKey(drawObject)) + if (!TransientResources.ContainsKey(gameObject)) { - TransientResources[drawObject] = new(); + TransientResources[gameObject] = new(); } if (filePath.StartsWith("|")) @@ -61,11 +61,11 @@ namespace MareSynchronos.Managers var newPath = filePath.ToLowerInvariant().Replace("\\", "/"); - if (filePath != gamePath && !TransientResources[drawObject].Contains(newPath)) + if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath)) { - TransientResources[drawObject].Add(newPath); - Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {drawObject}"); - TransientResourceLoaded?.Invoke(drawObject); + TransientResources[gameObject].Add(newPath); + Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {gameObject}"); + TransientResourceLoaded?.Invoke(gameObject); } } diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 093d1cf..25aef47 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.3.10 + 0.4.0 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/Models/PlayerRelatedObject.cs b/MareSynchronos/Models/PlayerRelatedObject.cs index 7d8e50e..34b6ad5 100644 --- a/MareSynchronos/Models/PlayerRelatedObject.cs +++ b/MareSynchronos/Models/PlayerRelatedObject.cs @@ -19,7 +19,18 @@ namespace MareSynchronos.Models public IntPtr Address { get; set; } public IntPtr DrawObjectAddress { get; set; } - private IntPtr CurrentAddress => getAddress.Invoke(); + private IntPtr CurrentAddress + { + get + { + try + { + return getAddress.Invoke(); + } + catch + { return IntPtr.Zero; } + } + } public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func getAddress) { diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 4e70830..850b001 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -28,11 +28,11 @@ namespace MareSynchronos.Utils public event LogOut? LogOut; public event FrameworkUpdate? FrameworkUpdate; - public unsafe bool IsDrawObjectPresent(IntPtr key) + public unsafe bool IsGameObjectPresent(IntPtr key) { foreach (var obj in _objectTable) { - if ((IntPtr)((GameObject*)obj.Address)->GetDrawObject() == key) + if (obj.Address == key) { return true; } @@ -105,7 +105,7 @@ namespace MareSynchronos.Utils public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--"; - public IntPtr PlayerPointer => _clientState.LocalPlayer!.Address; + public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero; public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!; From ce6764cbf890a1b128436c9547ab17919c3c6ef2 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 17:57:09 +0200 Subject: [PATCH 07/28] add some locking around modifiedfiles --- MareSynchronos/Managers/FileCacheManager.cs | 21 ++++++++++++++----- .../WebAPI/ApIController.Functions.Files.cs | 8 +++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index d415e3c..1ce838e 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -23,6 +23,7 @@ namespace MareSynchronos.Managers private readonly CancellationTokenSource _rescanTaskCancellationTokenSource = new(); private CancellationTokenSource _rescanTaskRunCancellationTokenSource = new(); private CancellationTokenSource? _scanCancellationTokenSource; + private object modifiedFilesLock = new object(); public FileCacheManager(IpcManager ipcManager, Configuration pluginConfiguration) { Logger.Verbose("Creating " + nameof(FileCacheManager)); @@ -142,7 +143,10 @@ namespace MareSynchronos.Managers private void OnModified(object sender, FileSystemEventArgs e) { - _modifiedFiles.Add(e.FullPath); + lock (modifiedFilesLock) + { + _modifiedFiles.Add(e.FullPath); + } _ = StartRescan(); } @@ -189,14 +193,21 @@ namespace MareSynchronos.Managers Logger.Debug("File changes detected"); - if (!_modifiedFiles.Any()) return; + lock (modifiedFilesLock) + { + if (!_modifiedFiles.Any()) return; + } _rescanTask = Task.Run(async () => { - var listCopy = _modifiedFiles.ToList(); - _modifiedFiles.Clear(); + List modifiedFilesCopy = new List(); + lock (modifiedFilesLock) + { + modifiedFilesCopy = _modifiedFiles.ToList(); + _modifiedFiles.Clear(); + } await using var db = new FileCacheContext(); - foreach (var item in listCopy.Distinct()) + foreach (var item in modifiedFilesCopy.Distinct()) { var fi = new FileInfo(item); if (!fi.Exists) diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 2c224be..e927e3f 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -123,9 +123,13 @@ namespace MareSynchronos.WebAPI { await using (var db = new FileCacheContext()) { - allFilesInDb = CurrentDownloads[currentDownloadId] + var fileCount = CurrentDownloads[currentDownloadId] .Where(c => c.CanBeTransferred) - .All(h => db.FileCaches.Any(f => f.Hash == h.Hash)); + .Count(h => db.FileCaches.Any(f => f.Hash == h.Hash)); + var totalFiles = CurrentDownloads[currentDownloadId].Count(c => c.CanBeTransferred); + Logger.Debug("Waiting for files to be in the DB, added " + fileCount + " of " + totalFiles); + + allFilesInDb = fileCount == totalFiles; } await Task.Delay(250, ct); From bd947d8f2a1ecb6776fb330bcf7f2403d216f084 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 22:06:28 +0200 Subject: [PATCH 08/28] add semi transient resource handling, wait max 3s for pets to change --- .../Factories/CharacterDataFactory.cs | 13 ++++- MareSynchronos/Managers/CachedPlayer.cs | 21 ++++++-- MareSynchronos/Managers/IpcManager.cs | 2 + MareSynchronos/Managers/PlayerManager.cs | 12 +++-- .../Managers/TransientResourceManager.cs | 52 +++++++++++++++++-- MareSynchronos/UI/CompactUI.cs | 2 +- MareSynchronos/Utils/DalamudUtil.cs | 15 +++--- 7 files changed, 95 insertions(+), 22 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index f5b1a08..8d14826 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -237,14 +237,15 @@ public class CharacterDataFactory previousData.FileReplacements[objectKind].Clear(); } - Stopwatch st = Stopwatch.StartNew(); var chara = _dalamudUtil.CreateGameObject(charaPointer)!; while (!_dalamudUtil.IsObjectPresent(chara)) { Logger.Verbose("Character is null but it shouldn't be, waiting"); Thread.Sleep(50); } - _dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); + //_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); + + Stopwatch st = Stopwatch.StartNew(); previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations(); @@ -339,6 +340,14 @@ public class CharacterDataFactory AddReplacement(item, objectKind, previousData, 1); } + foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) + { + Logger.Verbose("Found semi transient resource: " + item); + AddReplacement(item, objectKind, previousData, 1); + } + + transientResourceManager.PersistTransientResources(charaPointer, objectKind); + st.Stop(); Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); return previousData; diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 2845e46..004421d 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -8,7 +8,6 @@ using Dalamud.Logging; using FFXIVClientStructs.FFXIV.Client.Game.Character; using MareSynchronos.API; using MareSynchronos.FileCacheDB; -using MareSynchronos.Interop; using MareSynchronos.Models; using MareSynchronos.Utils; using MareSynchronos.WebAPI; @@ -250,6 +249,7 @@ public class CachedPlayer if (minionOrMount != null) { Logger.Debug($"Request Redraw for Minion/Mount"); + _dalamudUtil.WaitWhileCharacterIsDrawing((IntPtr)minionOrMount); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { _ipcManager.GlamourerApplyAll(glamourerData, obj: (IntPtr)minionOrMount); @@ -262,17 +262,29 @@ public class CachedPlayer } else if (objectKind == ObjectKind.Pet) { + int tick = 16; var pet = _dalamudUtil.GetPet(PlayerCharacter.Address); if (pet != IntPtr.Zero) { - Logger.Debug("Request Redraw for Pet"); + var totalWait = 0; + var newPet = IntPtr.Zero; + const int maxWait = 3000; + Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms"); + + do + { + Thread.Sleep(tick); + totalWait += tick; + newPet = _dalamudUtil.GetPet(PlayerCharacter.Address); + } while (newPet == pet && totalWait < maxWait); + if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { - _ipcManager.GlamourerApplyAll(glamourerData, pet); + _ipcManager.GlamourerApplyAll(glamourerData, newPet); } else { - _ipcManager.PenumbraRedraw(pet); + _ipcManager.PenumbraRedraw(newPet); } } } @@ -282,6 +294,7 @@ public class CachedPlayer if (companion != IntPtr.Zero) { Logger.Debug("Request Redraw for Companion"); + _dalamudUtil.WaitWhileCharacterIsDrawing(companion); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { _ipcManager.GlamourerApplyAll(glamourerData, companion); diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 0466e99..a651d72 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -137,6 +137,7 @@ namespace MareSynchronos.Managers var gameObj = _dalamudUtil.CreateGameObject(obj); if (gameObj != null) { + Logger.Verbose("Glamourer applying for " + gameObj); _glamourerApplyAll!.InvokeAction(customization, gameObj); } } @@ -205,6 +206,7 @@ namespace MareSynchronos.Managers var gameObj = _dalamudUtil.CreateGameObject(obj); if (gameObj != null) { + Logger.Verbose("Redrawing " + gameObj); _penumbraRedrawObject!.InvokeAction(gameObj, 0); } } diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 1616997..6e29937 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -61,11 +61,11 @@ namespace MareSynchronos.Managers }; } - public void HandleTransientResourceLoad(IntPtr drawObj) + public void HandleTransientResourceLoad(IntPtr gameObj) { foreach (var obj in playerRelatedObjects) { - if (obj.Address == drawObj && !obj.HasUnprocessedUpdate) + if (obj.Address == gameObj && !obj.HasUnprocessedUpdate) { obj.HasUnprocessedUpdate = true; OnPlayerOrAttachedObjectsChanged(); @@ -91,7 +91,7 @@ namespace MareSynchronos.Managers private unsafe void DalamudUtilOnFrameworkUpdate() { - //if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; + if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; //if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return; @@ -154,6 +154,7 @@ namespace MareSynchronos.Managers if (address == item.Address) { Logger.Debug("Penumbra redraw Event for " + item.ObjectKind); + _transientResourceManager.CleanSemiTransientResources(item.ObjectKind); item.HasUnprocessedUpdate = true; } } @@ -201,7 +202,10 @@ namespace MareSynchronos.Managers Task.Run(async () => { - _dalamudUtil.WaitWhileSelfIsDrawing(token); + foreach(var item in unprocessedObjects) + { + _dalamudUtil.WaitWhileCharacterIsDrawing(item.Address, token); + } CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token)); if (cacheDto == null || token.IsCancellationRequested) return; diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 857740a..e88dd03 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -1,4 +1,5 @@ -using MareSynchronos.Models; +using MareSynchronos.API; +using MareSynchronos.Models; using MareSynchronos.Utils; using System; using System.Collections.Generic; @@ -17,6 +18,7 @@ namespace MareSynchronos.Managers public event TransientResourceLoadedEvent? TransientResourceLoaded; private Dictionary> TransientResources { get; } = new(); + private Dictionary> SemiTransientResources { get; } = new(); public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil) { manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent; @@ -37,6 +39,14 @@ namespace MareSynchronos.Managers } } + public void CleanSemiTransientResources(ObjectKind objectKind) + { + if (SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind].Clear(); + } + } + public List GetTransientResources(IntPtr gameObject) { if (TransientResources.TryGetValue(gameObject, out var result)) @@ -47,6 +57,16 @@ namespace MareSynchronos.Managers return new List(); } + public List GetSemiTransientResources(ObjectKind objectKind) + { + if (SemiTransientResources.TryGetValue(objectKind, out var result)) + { + return result.ToList(); + } + + return new List(); + } + private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) { if (!TransientResources.ContainsKey(gameObject)) @@ -61,7 +81,7 @@ namespace MareSynchronos.Managers var newPath = filePath.ToLowerInvariant().Replace("\\", "/"); - if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath)) + if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Contains(newPath))) { TransientResources[gameObject].Add(newPath); Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {gameObject}"); @@ -69,14 +89,36 @@ namespace MareSynchronos.Managers } } - public void RemoveTransientResource(IntPtr drawObject, FileReplacement fileReplacement) + public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement) { - if (TransientResources.ContainsKey(drawObject)) + if (TransientResources.ContainsKey(gameObject)) { - TransientResources[drawObject].RemoveWhere(f => fileReplacement.ResolvedPath == f); + TransientResources[gameObject].RemoveWhere(f => fileReplacement.ResolvedPath == f); } } + public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind) + { + if (!SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind] = new HashSet(); + } + + if (!TransientResources.TryGetValue(gameObject, out var resources)) + { + return; + } + + var transientResources = resources.ToList(); + Logger.Debug("Persisting " + transientResources.Count + " transient resources"); + foreach (var item in transientResources) + { + SemiTransientResources[objectKind].Add(item); + } + + TransientResources[gameObject].Clear(); + } + public void Dispose() { dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate; diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index f3f5252..f50a774 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -36,7 +36,7 @@ namespace MareSynchronos.UI public CompactUi(WindowSystem windowSystem, UiShared uiShared, Configuration configuration, ApiController apiController) #if DEBUG - : base("Mare Synchronos " + new FileInfo(Assembly.GetExecutingAssembly().Location) .LastWriteTime.ToString("yyyyMMddHHmmss")+ "###MareSynchronosMainUI") + : base("Mare Synchronos " + new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime.ToString("yyyyMMddHHmmss")+ "###MareSynchronosMainUI") #else : base("Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI") #endif diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 850b001..47a39f2 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -140,23 +140,26 @@ namespace MareSynchronos.Utils public unsafe void WaitWhileCharacterIsDrawing(IntPtr characterAddress, CancellationToken? ct = null) { - if (!_clientState.IsLoggedIn) return; + if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return; var obj = (GameObject*)characterAddress; - + const int maxWaitTime = 10000; + const int tick = 250; + int curWaitTime = 0; // ReSharper disable once LoopVariableIsNeverChangedInsideLoop - while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true)) // 0b100000000000 is "still rendering" or something + while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < maxWaitTime) // 0b100000000000 is "still rendering" or something { Logger.Verbose("Waiting for character to finish drawing"); - Thread.Sleep(250); + curWaitTime += tick; + Thread.Sleep(tick); } if (ct?.IsCancellationRequested ?? false) return; // wait quarter a second just in case - Thread.Sleep(250); + Thread.Sleep(tick); } - public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? new IntPtr(), token); + public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? IntPtr.Zero, token); public void Dispose() { From 2e3f7aad2b55da06de8ad548bea82b3399b16834 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 22:54:58 +0200 Subject: [PATCH 09/28] potentially fix pets and summons --- .../Factories/CharacterDataFactory.cs | 18 +++++++- MareSynchronos/Managers/FileCacheManager.cs | 4 +- .../Managers/TransientResourceManager.cs | 44 +++++++++++++++---- MareSynchronos/Utils/DalamudUtil.cs | 9 ++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 8d14826..07e5777 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -334,6 +334,16 @@ public class CharacterDataFactory } } + if (objectKind == ObjectKind.Pet) + { + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.AddSemiTransientResource(objectKind, item); + } + + previousData.FileReplacements[objectKind].Clear(); + } + foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) { Logger.Verbose("Found transient resource: " + item); @@ -343,10 +353,14 @@ public class CharacterDataFactory foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) { Logger.Verbose("Found semi transient resource: " + item); - AddReplacement(item, objectKind, previousData, 1); + if (!previousData.FileReplacements.ContainsKey(objectKind)) + { + previousData.FileReplacements.Add(objectKind, new()); + } + previousData.FileReplacements[objectKind].Add(item); } - transientResourceManager.PersistTransientResources(charaPointer, objectKind); + transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement); st.Stop(); Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); diff --git a/MareSynchronos/Managers/FileCacheManager.cs b/MareSynchronos/Managers/FileCacheManager.cs index 1ce838e..b8ffcac 100644 --- a/MareSynchronos/Managers/FileCacheManager.cs +++ b/MareSynchronos/Managers/FileCacheManager.cs @@ -49,7 +49,7 @@ namespace MareSynchronos.Managers public string WatchedPenumbraDirectory => (_penumbraDirWatcher?.EnableRaisingEvents ?? false) ? _penumbraDirWatcher!.Path : "Not watched"; - public FileCache? Create(string file, CancellationToken token) + public FileCache? Create(string file, CancellationToken? token) { FileInfo fileInfo = new(file); int attempt = 0; @@ -57,7 +57,7 @@ namespace MareSynchronos.Managers { Thread.Sleep(1000); Logger.Debug("Waiting for file release " + fileInfo.FullName + " attempt " + attempt); - token.ThrowIfCancellationRequested(); + token?.ThrowIfCancellationRequested(); } if (attempt >= 10) return null; diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index e88dd03..31eb240 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -1,11 +1,10 @@ using MareSynchronos.API; +using MareSynchronos.FileCacheDB; using MareSynchronos.Models; using MareSynchronos.Utils; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MareSynchronos.Managers { @@ -15,16 +14,26 @@ namespace MareSynchronos.Managers { private readonly IpcManager manager; private readonly DalamudUtil dalamudUtil; + public event TransientResourceLoadedEvent? TransientResourceLoaded; private Dictionary> TransientResources { get; } = new(); - private Dictionary> SemiTransientResources { get; } = new(); + private Dictionary> SemiTransientResources { get; } = new(); public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil) { manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent; this.manager = manager; this.dalamudUtil = dalamudUtil; dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate; + dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged; + } + + private void DalamudUtil_ClassJobChanged() + { + if (SemiTransientResources.ContainsKey(ObjectKind.Pet)) + { + SemiTransientResources[ObjectKind.Pet].Clear(); + } } private void DalamudUtil_FrameworkUpdate() @@ -57,14 +66,14 @@ namespace MareSynchronos.Managers return new List(); } - public List GetSemiTransientResources(ObjectKind objectKind) + public List GetSemiTransientResources(ObjectKind objectKind) { if (SemiTransientResources.TryGetValue(objectKind, out var result)) { return result.ToList(); } - return new List(); + return new List(); } private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) @@ -81,7 +90,7 @@ namespace MareSynchronos.Managers var newPath = filePath.ToLowerInvariant().Replace("\\", "/"); - if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Contains(newPath))) + if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Any(f => f.ResolvedPath.ToLowerInvariant() == newPath.ToLowerInvariant()))) { TransientResources[gameObject].Add(newPath); Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {gameObject}"); @@ -97,11 +106,11 @@ namespace MareSynchronos.Managers } } - public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind) + public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func createFileReplacement) { if (!SemiTransientResources.ContainsKey(objectKind)) { - SemiTransientResources[objectKind] = new HashSet(); + SemiTransientResources[objectKind] = new HashSet(); } if (!TransientResources.TryGetValue(gameObject, out var resources)) @@ -113,7 +122,10 @@ namespace MareSynchronos.Managers Logger.Debug("Persisting " + transientResources.Count + " transient resources"); foreach (var item in transientResources) { - SemiTransientResources[objectKind].Add(item); + if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ToLowerInvariant())) + { + SemiTransientResources[objectKind].Add(createFileReplacement(item.ToLowerInvariant(), false)); + } } TransientResources[gameObject].Clear(); @@ -123,7 +135,21 @@ namespace MareSynchronos.Managers { dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate; manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent; + dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged; TransientResources.Clear(); } + + internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item) + { + if (!SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind] = new HashSet(); + } + + if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant())) + { + SemiTransientResources[objectKind].Add(item); + } + } } } diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 47a39f2..6fe751d 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -16,6 +16,7 @@ namespace MareSynchronos.Utils public delegate void LogIn(); public delegate void LogOut(); + public delegate void ClassJobChanged(); public delegate void FrameworkUpdate(); @@ -27,6 +28,8 @@ namespace MareSynchronos.Utils public event LogIn? LogIn; public event LogOut? LogOut; public event FrameworkUpdate? FrameworkUpdate; + public event ClassJobChanged? ClassJobChanged; + private uint? classJobId = 0; public unsafe bool IsGameObjectPresent(IntPtr key) { @@ -51,12 +54,18 @@ namespace MareSynchronos.Utils _framework.Update += FrameworkOnUpdate; if (IsLoggedIn) { + classJobId = _clientState.LocalPlayer!.ClassJob.Id; ClientStateOnLogin(null, EventArgs.Empty); } } private void FrameworkOnUpdate(Framework framework) { + if(_clientState.LocalPlayer != null && _clientState.LocalPlayer.ClassJob.Id != classJobId) + { + classJobId = _clientState.LocalPlayer.ClassJob.Id; + ClassJobChanged?.Invoke(); + } FrameworkUpdate?.Invoke(); } From 2f5159cd4bcbe3e8e590d5e2189a9b4a21cb235b Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 3 Sep 2022 23:54:26 +0200 Subject: [PATCH 10/28] small fixes for waiting --- MareSynchronos/Factories/CharacterDataFactory.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 07e5777..ec1fd26 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -146,7 +146,6 @@ public class CharacterDataFactory return; } - //Logger.Verbose("Adding File Replacement for Material " + fileName); var mtrlPath = fileName.Split("|")[2]; if (cache.FileReplacements.ContainsKey(objectKind)) @@ -185,8 +184,6 @@ public class CharacterDataFactory { if (varPath.IsNullOrEmpty()) return; - //Logger.Verbose("Adding File Replacement for Texture " + texPath); - if (cache.FileReplacements.ContainsKey(objectKind)) { if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath))) @@ -205,8 +202,6 @@ public class CharacterDataFactory { if (string.IsNullOrEmpty(texPath)) return; - //Logger.Verbose("Adding File Replacement for Texture " + texPath); - if (cache.FileReplacements.ContainsKey(objectKind)) { if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath))) @@ -243,7 +238,8 @@ public class CharacterDataFactory Logger.Verbose("Character is null but it shouldn't be, waiting"); Thread.Sleep(50); } - //_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); + + _dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); Stopwatch st = Stopwatch.StartNew(); @@ -357,7 +353,11 @@ public class CharacterDataFactory { previousData.FileReplacements.Add(objectKind, new()); } - previousData.FileReplacements[objectKind].Add(item); + + if (!previousData.FileReplacements[objectKind].Any(k => k.ResolvedPath == item.ResolvedPath)) + { + previousData.FileReplacements[objectKind].Add(item); + } } transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement); From ddb762c698094aa8c4a73d724f4c88bf286137c4 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sun, 4 Sep 2022 21:17:52 +0200 Subject: [PATCH 11/28] add fileswaps first test --- MareAPI | 2 +- .../Factories/CharacterDataFactory.cs | 6 ++-- MareSynchronos/Managers/CachedPlayer.cs | 13 +++++++- MareSynchronos/Managers/PlayerManager.cs | 5 ++- .../Managers/TransientResourceManager.cs | 12 ++++--- MareSynchronos/Models/CharacterData.cs | 33 ++++++++++++++----- MareSynchronos/Models/FileReplacement.cs | 8 +++-- MareSynchronos/UI/CompactUI.cs | 20 +++++++---- MareSynchronos/UI/IntroUI.cs | 7 ++-- .../WebAPI/ApIController.Functions.Files.cs | 2 +- 10 files changed, 77 insertions(+), 31 deletions(-) diff --git a/MareAPI b/MareAPI index 645dd0b..9bb99a5 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 645dd0b18f815cb7ccd81f37e84ded9cf650c3f3 +Subproject commit 9bb99a5e68196165b9c1360acff198c4731164d1 diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index ec1fd26..e6c420b 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -180,7 +180,7 @@ public class CharacterDataFactory } } - private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0) + private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = false) { if (varPath.IsNullOrEmpty()) return; @@ -192,7 +192,7 @@ public class CharacterDataFactory } } - var variousReplacement = CreateFileReplacement(varPath, false); + var variousReplacement = CreateFileReplacement(varPath, doNotReverseResolve); DebugPrint(variousReplacement, objectKind, "Various", inheritanceLevel); cache.AddFileReplacement(objectKind, variousReplacement); @@ -343,7 +343,7 @@ public class CharacterDataFactory foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) { Logger.Verbose("Found transient resource: " + item); - AddReplacement(item, objectKind, previousData, 1); + AddReplacement(item, objectKind, previousData, 1, true); } foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 004421d..95c167f 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -11,6 +11,7 @@ using MareSynchronos.FileCacheDB; using MareSynchronos.Models; using MareSynchronos.Utils; using MareSynchronos.WebAPI; +using Newtonsoft.Json; namespace MareSynchronos.Managers; @@ -141,6 +142,7 @@ public class CachedPlayer { Dictionary moddedPaths; int attempts = 0; + //Logger.Verbose(JsonConvert.SerializeObject(_cachedData, Formatting.Indented)); while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10) { Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind); @@ -192,7 +194,7 @@ public class CachedPlayer try { using var db = new FileCacheContext(); - foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value).ToList()) + foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList()) { foreach (var gamePath in item.GamePaths) { @@ -207,6 +209,15 @@ public class CachedPlayer } } } + + foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList()) + { + foreach (var gamePath in item.GamePaths) + { + Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath); + moddedDictionary[gamePath] = item.FileSwapPath; + } + } } catch (Exception ex) { diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 6e29937..c208585 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using System.Collections.Generic; using System.Linq; using MareSynchronos.Models; +using Newtonsoft.Json; namespace MareSynchronos.Managers { @@ -142,7 +143,9 @@ namespace MareSynchronos.Managers Logger.Verbose("Cache creation complete"); - return PermanentDataCache.ToCharacterCacheDto(); + var cache = PermanentDataCache.ToCharacterCacheDto(); + //Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented)); + return cache; } private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx) diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 31eb240..03401ba 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -88,12 +88,12 @@ namespace MareSynchronos.Managers filePath = filePath.Split("|")[2]; } - var newPath = filePath.ToLowerInvariant().Replace("\\", "/"); + var newPath = gamePath.ToLowerInvariant().Replace("\\", "/"); - if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Any(f => f.ResolvedPath.ToLowerInvariant() == newPath.ToLowerInvariant()))) + if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == newPath.ToLowerInvariant()))) { TransientResources[gameObject].Add(newPath); - Logger.Debug($"Adding {filePath.ToLowerInvariant().Replace("\\", "/")} for {gameObject}"); + Logger.Debug($"Adding {newPath.ToLowerInvariant().Replace("\\", "/")} for {gameObject} ({filePath})"); TransientResourceLoaded?.Invoke(gameObject); } } @@ -124,7 +124,11 @@ namespace MareSynchronos.Managers { if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ToLowerInvariant())) { - SemiTransientResources[objectKind].Add(createFileReplacement(item.ToLowerInvariant(), false)); + Logger.Debug("Persisting " + item.ToLowerInvariant()); + var fileReplacement = createFileReplacement(item.ToLowerInvariant(), true); + if (!fileReplacement.HasFileReplacement) + fileReplacement = createFileReplacement(item.ToLowerInvariant(), false); + SemiTransientResources[objectKind].Add(fileReplacement); } } diff --git a/MareSynchronos/Models/CharacterData.cs b/MareSynchronos/Models/CharacterData.cs index 4a76b7a..27726cf 100644 --- a/MareSynchronos/Models/CharacterData.cs +++ b/MareSynchronos/Models/CharacterData.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using MareSynchronos.API; +using MareSynchronos.Utils; +using Lumina.Excel.GeneratedSheets; namespace MareSynchronos.Models { @@ -39,16 +41,31 @@ namespace MareSynchronos.Models public CharacterCacheDto ToCharacterCacheDto() { + var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g => + { + return new FileReplacementDto() + { + GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(), + Hash = g.First().Hash, + }; + }).ToList()); + + Logger.Debug("Adding fileSwaps"); + foreach (var item in FileReplacements) + { + Logger.Debug("Checking fileSwaps for " + item.Key); + var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto()); + Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps"); + foreach (var swap in fileSwapsToAdd) + { + Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath); + } + fileReplacements[item.Key].AddRange(fileSwapsToAdd); + } + return new CharacterCacheDto() { - FileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement).GroupBy(f => f.Hash).Select(g => - { - return new FileReplacementDto() - { - GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(), - Hash = g.First().Hash - }; - }).ToList()), + FileReplacements = fileReplacements, GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value), ManipulationData = ManipulationString }; diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index 73260c5..3d82d00 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -8,6 +8,7 @@ using MareSynchronos.FileCacheDB; using System.IO; using MareSynchronos.API; using MareSynchronos.Utils; +using System.Text.RegularExpressions; namespace MareSynchronos.Models { @@ -20,12 +21,14 @@ namespace MareSynchronos.Models _penumbraDirectory = penumbraDirectory; } - public bool Computed => !HasFileReplacement || !string.IsNullOrEmpty(Hash); + public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash); public List GamePaths { get; set; } = new(); public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath); + public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath; + public string Hash { get; set; } = string.Empty; public string ResolvedPath { get; set; } = string.Empty; @@ -33,7 +36,7 @@ namespace MareSynchronos.Models public void SetResolvedPath(string path) { ResolvedPath = path.ToLowerInvariant().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/'); - if (!HasFileReplacement) return; + if (!HasFileReplacement || IsFileSwap) return; _ = Task.Run(() => { @@ -73,6 +76,7 @@ namespace MareSynchronos.Models { GamePaths = GamePaths.ToArray(), Hash = Hash, + FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty }; } public override string ToString() diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index f50a774..cccdec9 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -34,16 +34,22 @@ namespace MareSynchronos.UI private float _windowContentWidth = 0; public CompactUi(WindowSystem windowSystem, - UiShared uiShared, Configuration configuration, ApiController apiController) -#if DEBUG - : base("Mare Synchronos " + new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime.ToString("yyyyMMddHHmmss")+ "###MareSynchronosMainUI") -#else - : base("Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI") -#endif - + UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") { Logger.Verbose("Creating " + nameof(CompactUi)); +#if DEBUG + string dateTime = "DEV VERSION"; + try + { + dateTime = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime.ToString("yyyyMMddHHmmss"); + } + catch { } + this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; +#else + this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI"; +#endif + _windowSystem = windowSystem; _uiShared = uiShared; _configuration = configuration; diff --git a/MareSynchronos/UI/IntroUI.cs b/MareSynchronos/UI/IntroUI.cs index 6997cb9..053bf09 100644 --- a/MareSynchronos/UI/IntroUI.cs +++ b/MareSynchronos/UI/IntroUI.cs @@ -40,9 +40,10 @@ namespace MareSynchronos.UI private Dictionary _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } }; private int _currentLanguage; - private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1 - && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2 - && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3; + private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim() + && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim() + && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim(); + public void Dispose() { diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index e927e3f..4d8a0a7 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -150,7 +150,7 @@ namespace MareSynchronos.WebAPI Logger.Verbose("New Token Created"); List unverifiedUploadHashes = new(); - foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Select(v => v.Hash).Distinct()).Distinct().ToList()) + foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList()) { if (!_verifiedUploadedHashes.Contains(item)) { From a2f88124d71d921edac6fcca86b9155db75dee88 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 5 Sep 2022 11:23:02 +0200 Subject: [PATCH 12/28] move handling of critical ipc to main thread --- MareSynchronos/Managers/IpcManager.cs | 80 ++++++++++++++++++--------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index a651d72..4e89693 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -34,6 +34,7 @@ namespace MareSynchronos.Managers _penumbraSetTemporaryMod; private readonly ICallGateSubscriber _penumbraGameObjectResourcePathResolved; private readonly DalamudUtil _dalamudUtil; + private readonly Queue actionQueue = new(); public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) { @@ -80,6 +81,15 @@ namespace MareSynchronos.Managers } this._dalamudUtil = dalamudUtil; + _dalamudUtil.FrameworkUpdate += HandleActionQueue; + } + + private void HandleActionQueue() + { + while (actionQueue.TryDequeue(out var action)) + { + action(); + } } private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) @@ -125,6 +135,9 @@ namespace MareSynchronos.Managers { Logger.Verbose("Disposing " + nameof(IpcManager)); + _dalamudUtil.FrameworkUpdate -= HandleActionQueue; + actionQueue.Clear(); + _penumbraDispose.Unsubscribe(PenumbraDispose); _penumbraInit.Unsubscribe(PenumbraInit); _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); @@ -134,26 +147,35 @@ namespace MareSynchronos.Managers public void GlamourerApplyAll(string? customization, IntPtr obj) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj != null) + actionQueue.Enqueue(() => { - Logger.Verbose("Glamourer applying for " + gameObj); - _glamourerApplyAll!.InvokeAction(customization, gameObj); - } + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj != null) + { + Logger.Verbose("Glamourer applying for " + gameObj); + _glamourerApplyAll!.InvokeAction(customization, gameObj); + } + }); } public void GlamourerApplyOnlyEquipment(string customization, GameObject character) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - Logger.Verbose("Glamourer apply only equipment to " + character); - _glamourerApplyOnlyEquipment!.InvokeAction(customization, character); + actionQueue.Enqueue(() => + { + Logger.Verbose("Glamourer apply only equipment to " + character); + _glamourerApplyOnlyEquipment!.InvokeAction(customization, character); + }); } public void GlamourerApplyOnlyCustomization(string customization, GameObject character) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - Logger.Verbose("Glamourer apply only customization to " + character); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, character); + actionQueue.Enqueue(() => + { + Logger.Verbose("Glamourer apply only customization to " + character); + _glamourerApplyOnlyCustomization!.InvokeAction(customization, character); + }); } public string GlamourerGetCharacterCustomization(GameObject character) @@ -177,7 +199,7 @@ namespace MareSynchronos.Managers public void GlamourerRevertCharacterCustomization(GameObject character) { if (!CheckGlamourerApi()) return; - _glamourerRevertCustomization!.InvokeAction(character); + actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character)); } public string PenumbraCreateTemporaryCollection(string characterName) @@ -203,32 +225,37 @@ namespace MareSynchronos.Managers public void PenumbraRedraw(IntPtr obj) { if (!CheckPenumbraApi()) return; - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj != null) + actionQueue.Enqueue(() => { - Logger.Verbose("Redrawing " + gameObj); - _penumbraRedrawObject!.InvokeAction(gameObj, 0); - } + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj != null) + { + Logger.Verbose("Redrawing " + gameObj); + _penumbraRedrawObject!.InvokeAction(gameObj, 0); + } + }); } public void PenumbraRedraw(string actorName) { if (!CheckPenumbraApi()) return; - _penumbraRedraw!.InvokeAction(actorName, 0); + actionQueue.Enqueue(() => _penumbraRedraw!.InvokeAction(actorName, 0)); } public void PenumbraRemoveTemporaryCollection(string characterName) { if (!CheckPenumbraApi()) return; - Logger.Verbose("Removing temp collection for " + characterName); - _penumbraRemoveTemporaryCollection.InvokeFunc(characterName); + actionQueue.Enqueue(() => + { + Logger.Verbose("Removing temp collection for " + characterName); + _penumbraRemoveTemporaryCollection.InvokeFunc(characterName); + }); } public string? PenumbraResolvePath(string path) { if (!CheckPenumbraApi()) return null; var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path); - //Logger.Verbose("Resolved " + path + "=>" + string.Join(", ", resolvedPath)); return resolvedPath; } @@ -240,7 +267,6 @@ namespace MareSynchronos.Managers { resolvedPaths = new[] { path }; } - //Logger.Verbose("Reverse Resolved " + path + "=>" + string.Join(", ", resolvedPaths)); return resolvedPaths; } @@ -248,12 +274,15 @@ namespace MareSynchronos.Managers { if (!CheckPenumbraApi()) return; - Logger.Verbose("Assigning temp mods for " + collectionName); - foreach (var mod in modPaths) + actionQueue.Enqueue(() => { - Logger.Verbose(mod.Key + " => " + mod.Value); - } - var ret = _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", collectionName, modPaths, manipulationData, 0); + Logger.Verbose("Assigning temp mods for " + collectionName); + foreach (var mod in modPaths) + { + Logger.Verbose(mod.Key + " => " + mod.Value); + } + _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", collectionName, modPaths, manipulationData, 0); + }); } private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) @@ -270,6 +299,7 @@ namespace MareSynchronos.Managers private void PenumbraDispose() { PenumbraDisposed?.Invoke(); + actionQueue.Clear(); } } } From 4a0dce0e4450816209307ffbc76c8f315095fcf2 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 6 Sep 2022 13:20:39 +0200 Subject: [PATCH 13/28] minor fixes --- MareSynchronos/Managers/CachedPlayer.cs | 3 +-- MareSynchronos/Managers/IpcManager.cs | 9 ++++--- MareSynchronos/MareSynchronos.csproj | 4 ++++ MareSynchronos/UI/CompactUI.cs | 9 +++++-- MareSynchronos/UI/DownloadUi.cs | 2 +- MareSynchronos/Utils/Various.cs | 32 +++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 MareSynchronos/Utils/Various.cs diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 95c167f..2b91f4e 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -230,8 +230,7 @@ public class CachedPlayer private void ApplyBaseData(Dictionary moddedPaths) { _ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!); - var tempCollection = _ipcManager.PenumbraCreateTemporaryCollection(PlayerName!); - _ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, _cachedData.ManipulationData); + _ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData); } private unsafe void ApplyCustomizationData(ObjectKind objectKind) diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 4e89693..c2b7304 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using Dalamud.Game.ClientState.Objects.Types; using MareSynchronos.Utils; using MareSynchronos.WebAPI; +using Lumina.Excel.GeneratedSheets; +using Action = System.Action; namespace MareSynchronos.Managers { @@ -270,18 +272,19 @@ namespace MareSynchronos.Managers return resolvedPaths; } - public void PenumbraSetTemporaryMods(string collectionName, Dictionary modPaths, string manipulationData) + public void PenumbraSetTemporaryMods(string characterName, Dictionary modPaths, string manipulationData) { if (!CheckPenumbraApi()) return; actionQueue.Enqueue(() => { - Logger.Verbose("Assigning temp mods for " + collectionName); + var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); + Logger.Verbose("Assigning temp mods for " + ret.Item2); foreach (var mod in modPaths) { Logger.Verbose(mod.Key + " => " + mod.Value); } - _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", collectionName, modPaths, manipulationData, 0); + _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", ret.Item2, modPaths, manipulationData, 0); }); } diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 25aef47..277faca 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -32,6 +32,10 @@ + + build$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss:fffZ")) + + diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index cccdec9..f7011f6 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -42,9 +42,14 @@ namespace MareSynchronos.UI string dateTime = "DEV VERSION"; try { - dateTime = new FileInfo(Assembly.GetExecutingAssembly().Location).LastWriteTime.ToString("yyyyMMddHHmmss"); + dateTime = VariousExtensions.GetLinkerTime(Assembly.GetCallingAssembly()).ToString("yyyyMMddHHmmss"); + } + catch (Exception ex) + { + Logger.Warn("Could not get assembly name"); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace); } - catch { } this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; #else this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI"; diff --git a/MareSynchronos/UI/DownloadUi.cs b/MareSynchronos/UI/DownloadUi.cs index fe30ce1..9e9f05f 100644 --- a/MareSynchronos/UI/DownloadUi.cs +++ b/MareSynchronos/UI/DownloadUi.cs @@ -101,7 +101,7 @@ public class DownloadUi : Window, IDisposable if (_apiController.CurrentDownloads.Any()) { - var currentDownloads = _apiController.CurrentDownloads.SelectMany(k => k.Value).ToList(); + var currentDownloads = _apiController.CurrentDownloads.Where(d => d.Value != null && d.Value.Any()).ToList().SelectMany(k => k.Value).ToList(); var multBase = currentDownloads.Any() ? 0 : 2; var doneDownloads = currentDownloads.Count(c => c.IsTransferred); var totalDownloads = currentDownloads.Count; diff --git a/MareSynchronos/Utils/Various.cs b/MareSynchronos/Utils/Various.cs new file mode 100644 index 0000000..fa96e17 --- /dev/null +++ b/MareSynchronos/Utils/Various.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace MareSynchronos.Utils +{ + public static class VariousExtensions + { + public static DateTime GetLinkerTime(Assembly assembly) + { + const string BuildVersionMetadataPrefix = "+build"; + + var attribute = assembly.GetCustomAttribute(); + if (attribute?.InformationalVersion != null) + { + var value = attribute.InformationalVersion; + var index = value.IndexOf(BuildVersionMetadataPrefix); + if (index > 0) + { + value = value[(index + BuildVersionMetadataPrefix.Length)..]; + return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture); + } + } + + return default; + } + } +} From 7265dab8a770fb7e56b9e4049c185b9dd600cb68 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 6 Sep 2022 13:39:33 +0200 Subject: [PATCH 14/28] change downloads to concurrentdictionary --- MareSynchronos/Managers/CachedPlayer.cs | 1 + MareSynchronos/UI/CompactUI.cs | 2 +- MareSynchronos/WebAPI/ApIController.Functions.Files.cs | 9 ++++++--- MareSynchronos/WebAPI/ApiController.Connectivity.cs | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 2b91f4e..689afd2 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -147,6 +147,7 @@ public class CachedPlayer { Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind); await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken); + _apiController.CancelDownload(downloadId); if (downloadToken.IsCancellationRequested) { Logger.Verbose("Detected cancellation"); diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index f7011f6..946030e 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -378,7 +378,7 @@ namespace MareSynchronos.UI if (currentDownloads.Any()) { - var totalDownloads = currentDownloads.Count; + var totalDownloads = currentDownloads.Count(); var doneDownloads = currentDownloads.Count(c => c.IsTransferred); var totalDownloaded = currentDownloads.Sum(c => c.Transferred); var totalToDownload = currentDownloads.Sum(c => c.Total); diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 4d8a0a7..8b0532b 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -95,7 +95,7 @@ namespace MareSynchronos.WebAPI { File.Delete(tempFile); Logger.Debug("Detected cancellation, removing " + currentDownloadId); - CurrentDownloads.Remove(currentDownloadId); + CancelDownload(currentDownloadId); break; } @@ -136,7 +136,7 @@ namespace MareSynchronos.WebAPI } Logger.Debug("Download complete, removing " + currentDownloadId); - CurrentDownloads.Remove(currentDownloadId); + CancelDownload(currentDownloadId); } public async Task PushCharacterData(CharacterCacheDto character, List visibleCharacterIds) @@ -292,7 +292,10 @@ namespace MareSynchronos.WebAPI public void CancelDownload(int downloadId) { - CurrentDownloads.Remove(downloadId); + while (CurrentDownloads.ContainsKey(downloadId)) + { + CurrentDownloads.TryRemove(downloadId, out _); + } } } diff --git a/MareSynchronos/WebAPI/ApiController.Connectivity.cs b/MareSynchronos/WebAPI/ApiController.Connectivity.cs index 3f3b074..491299b 100644 --- a/MareSynchronos/WebAPI/ApiController.Connectivity.cs +++ b/MareSynchronos/WebAPI/ApiController.Connectivity.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -93,7 +94,7 @@ namespace MareSynchronos.WebAPI public event SimpleStringDelegate? UnpairedFromOther; - public Dictionary> CurrentDownloads { get; } = new(); + public ConcurrentDictionary> CurrentDownloads { get; } = new(); public List CurrentUploads { get; } = new(); From 99df88bb3b44f3949dda466e642b88e6762e5433 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 6 Sep 2022 14:58:16 +0200 Subject: [PATCH 15/28] re-check semi-persistent transient resources validity instead of clearing on redraw --- .../Factories/CharacterDataFactory.cs | 22 ++++++++++++++----- MareSynchronos/Managers/IpcManager.cs | 6 ++--- MareSynchronos/Managers/PlayerManager.cs | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index e6c420b..c5c8c62 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -238,7 +238,7 @@ public class CharacterDataFactory Logger.Verbose("Character is null but it shouldn't be, waiting"); Thread.Sleep(50); } - + _dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); Stopwatch st = Stopwatch.StartNew(); @@ -342,21 +342,31 @@ public class CharacterDataFactory foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) { - Logger.Verbose("Found transient resource: " + item); - AddReplacement(item, objectKind, previousData, 1, true); + if (!previousData.FileReplacements[objectKind].Any(f => f.GamePaths.Any(p => p.ToLowerInvariant() == item.ToLowerInvariant()))) + { + Logger.Verbose("Found transient resource: " + item); + AddReplacement(item, objectKind, previousData, 1, true); + } } foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) { - Logger.Verbose("Found semi transient resource: " + item); if (!previousData.FileReplacements.ContainsKey(objectKind)) { previousData.FileReplacements.Add(objectKind, new()); } - if (!previousData.FileReplacements[objectKind].Any(k => k.ResolvedPath == item.ResolvedPath)) + if (!previousData.FileReplacements[objectKind].Any(k => k.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant())) { - previousData.FileReplacements[objectKind].Add(item); + if (_ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant() == item.GamePaths.First().ToLowerInvariant()) + { + transientResourceManager.RemoveTransientResource(charaPointer, item); + } + else + { + Logger.Verbose("Found semi transient resource: " + item); + previousData.FileReplacements[objectKind].Add(item); + } } } diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index c2b7304..b8a1127 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -254,11 +254,11 @@ namespace MareSynchronos.Managers }); } - public string? PenumbraResolvePath(string path) + public string PenumbraResolvePath(string path) { - if (!CheckPenumbraApi()) return null; + if (!CheckPenumbraApi()) return path; var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path); - return resolvedPath; + return resolvedPath ?? path; } public string[] PenumbraReverseResolvePlayer(string path) diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index c208585..ec1ac93 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -157,7 +157,7 @@ namespace MareSynchronos.Managers if (address == item.Address) { Logger.Debug("Penumbra redraw Event for " + item.ObjectKind); - _transientResourceManager.CleanSemiTransientResources(item.ObjectKind); + //_transientResourceManager.CleanSemiTransientResources(item.ObjectKind); item.HasUnprocessedUpdate = true; } } From 1e9161aed582d1980b0b1aced3cdf9e6e70a91ef Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 6 Sep 2022 16:15:08 +0200 Subject: [PATCH 16/28] fix adding semi transient resources, fix unloading not unloading mods due to queue --- .../Factories/CharacterDataFactory.cs | 143 ++++++++++-------- MareSynchronos/Managers/IpcManager.cs | 7 + .../Managers/TransientResourceManager.cs | 29 +++- 3 files changed, 107 insertions(+), 72 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index c5c8c62..a00df6d 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -268,66 +268,7 @@ public class CharacterDataFactory if (objectKind == ObjectKind.Player) { - var weaponObject = (Weapon*)((Object*)human)->ChildObject; - - if ((IntPtr)weaponObject != IntPtr.Zero) - { - var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel; - - AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0); - - foreach (var item in previousData.FileReplacements[objectKind]) - { - transientResourceManager.RemoveTransientResource(charaPointer, item); - } - - foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) - { - Logger.Verbose("Found transient weapon resource: " + item); - AddReplacementsFromTexture(item, objectKind, previousData, 0, false); - } - - if (weaponObject->NextSibling != (IntPtr)weaponObject) - { - var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel; - - AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1); - - foreach (var item in previousData.FileReplacements[objectKind]) - { - transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item); - } - - foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) - { - Logger.Verbose("Found transient offhand weapon resource: " + item); - AddReplacement(item, objectKind, previousData, 1); - } - } - } - - AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData); - try - { - AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false); - } - catch - { - Logger.Warn("Could not get Decal data"); - } - try - { - AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false); - } - catch - { - Logger.Warn("Could not get Legacy Body Decal Data"); - } - - foreach (var item in previousData.FileReplacements[objectKind]) - { - transientResourceManager.RemoveTransientResource(charaPointer, item); - } + AddPlayerSpecificReplacements(previousData, objectKind, charaPointer, human); } if (objectKind == ObjectKind.Pet) @@ -340,14 +281,26 @@ public class CharacterDataFactory previousData.FileReplacements[objectKind].Clear(); } - foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) + ManageSemiTransientData(previousData, objectKind, charaPointer); + + st.Stop(); + Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); + return previousData; + } + + private unsafe void ManageSemiTransientData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer) + { + transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement); + + /*foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) { if (!previousData.FileReplacements[objectKind].Any(f => f.GamePaths.Any(p => p.ToLowerInvariant() == item.ToLowerInvariant()))) { Logger.Verbose("Found transient resource: " + item); AddReplacement(item, objectKind, previousData, 1, true); } - } + }*/ + foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) { @@ -369,12 +322,70 @@ public class CharacterDataFactory } } } + } - transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement); + private unsafe void AddPlayerSpecificReplacements(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer, Human* human) + { + var weaponObject = (Weapon*)((Object*)human)->ChildObject; - st.Stop(); - Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed); - return previousData; + if ((IntPtr)weaponObject != IntPtr.Zero) + { + var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel; + + AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0); + + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource(charaPointer, item); + } + + foreach (var item in transientResourceManager.GetTransientResources((IntPtr)weaponObject)) + { + Logger.Verbose("Found transient weapon resource: " + item); + AddReplacement(item, objectKind, previousData, 1, true); + } + + if (weaponObject->NextSibling != (IntPtr)weaponObject) + { + var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel; + + AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1); + + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item); + } + + foreach (var item in transientResourceManager.GetTransientResources((IntPtr)offHandWeapon)) + { + Logger.Verbose("Found transient offhand weapon resource: " + item); + AddReplacement(item, objectKind, previousData, 1, true); + } + } + } + + AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData); + try + { + AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false); + } + catch + { + Logger.Warn("Could not get Decal data"); + } + try + { + AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false); + } + catch + { + Logger.Warn("Could not get Legacy Body Decal Data"); + } + + foreach (var item in previousData.FileReplacements[objectKind]) + { + transientResourceManager.RemoveTransientResource(charaPointer, item); + } } private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache) diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index b8a1127..b436d53 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -137,6 +137,13 @@ namespace MareSynchronos.Managers { Logger.Verbose("Disposing " + nameof(IpcManager)); + int totalSleepTime = 0; + while (actionQueue.Count > 0 && totalSleepTime < 2000) + { + System.Threading.Thread.Sleep(16); + totalSleepTime += 16; + } + _dalamudUtil.FrameworkUpdate -= HandleActionQueue; actionQueue.Clear(); diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 03401ba..848c42d 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -88,12 +88,22 @@ namespace MareSynchronos.Managers filePath = filePath.Split("|")[2]; } - var newPath = gamePath.ToLowerInvariant().Replace("\\", "/"); + filePath = filePath.ToLowerInvariant(); - if (filePath != gamePath && !TransientResources[gameObject].Contains(newPath) && !SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == newPath.ToLowerInvariant()))) + var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/"); + + if (TransientResources[gameObject].Contains(replacedGamePath) || + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath + && f.ResolvedPath.ToLowerInvariant() == filePath))) { - TransientResources[gameObject].Add(newPath); - Logger.Debug($"Adding {newPath.ToLowerInvariant().Replace("\\", "/")} for {gameObject} ({filePath})"); + Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); + Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath + && f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); + } + else + { + TransientResources[gameObject].Add(replacedGamePath); + Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})"); TransientResourceLoaded?.Invoke(gameObject); } } @@ -102,7 +112,7 @@ namespace MareSynchronos.Managers { if (TransientResources.ContainsKey(gameObject)) { - TransientResources[gameObject].RemoveWhere(f => fileReplacement.ResolvedPath == f); + TransientResources[gameObject].RemoveWhere(f => fileReplacement.ResolvedPath.ToLowerInvariant() == f.ToLowerInvariant()); } } @@ -122,7 +132,14 @@ namespace MareSynchronos.Managers Logger.Debug("Persisting " + transientResources.Count + " transient resources"); foreach (var item in transientResources) { - if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ToLowerInvariant())) + var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant()); + if (existingResource) + { + Logger.Debug("Semi Transient resource replaced: " + item); + SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant()); + } + + if (!SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant())) { Logger.Debug("Persisting " + item.ToLowerInvariant()); var fileReplacement = createFileReplacement(item.ToLowerInvariant(), true); From 23abae4c2094ed6fa448d2d75e9c9d6af1750bf7 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 7 Sep 2022 02:41:25 +0200 Subject: [PATCH 17/28] minor adjustments for potential concurrency issues --- .../Factories/CharacterDataFactory.cs | 10 ---- MareSynchronos/Managers/CachedPlayer.cs | 4 +- .../Managers/OnlinePlayerManager.cs | 54 ++++++++++++------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index a00df6d..6ea9eae 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -292,16 +292,6 @@ public class CharacterDataFactory { transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement); - /*foreach (var item in transientResourceManager.GetTransientResources(charaPointer)) - { - if (!previousData.FileReplacements[objectKind].Any(f => f.GamePaths.Any(p => p.ToLowerInvariant() == item.ToLowerInvariant()))) - { - Logger.Verbose("Found transient resource: " + item); - AddReplacement(item, objectKind, previousData, 1, true); - } - }*/ - - foreach (var item in transientResourceManager.GetSemiTransientResources(objectKind)) { if (!previousData.FileReplacements.ContainsKey(objectKind)) diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 689afd2..5d917f1 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -40,7 +40,7 @@ public class CachedPlayer } } - private bool _isDisposed = false; + private bool _isDisposed = true; private CancellationTokenSource? _downloadCancellationTokenSource = new(); private string _lastGlamourerData = string.Empty; @@ -206,6 +206,7 @@ public class CachedPlayer } else { + Logger.Verbose("Missing file: " + item.Hash); missingFiles.Add(item); } } @@ -397,6 +398,7 @@ public class CachedPlayer public void InitializePlayer(PlayerCharacter character, CharacterCacheDto? cache) { + if (!_isDisposed) return; Logger.Debug("Initializing Player " + this + " has cache: " + (cache != null)); IsVisible = true; PlayerName = character.Name.ToString(); diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index 30f0ee9..6c28b2e 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -8,7 +9,6 @@ using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI; using MareSynchronos.WebAPI.Utils; -using Newtonsoft.Json; namespace MareSynchronos.Managers; @@ -19,12 +19,12 @@ public class OnlinePlayerManager : IDisposable private readonly Framework _framework; private readonly IpcManager _ipcManager; private readonly PlayerManager _playerManager; - private readonly List _onlineCachedPlayers = new(); + private readonly ConcurrentDictionary _onlineCachedPlayers = new(); private readonly Dictionary _temporaryStoredCharacterCache = new(); private readonly Dictionary _playerTokenDisposal = new(); - private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Where(p => p.PlayerCharacter != null) - .Select(p => p.PlayerNameHash).ToList(); + private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Where(p => p.Value.PlayerCharacter != null) + .Select(p => p.Value.PlayerNameHash).ToList(); private DateTime _lastPlayerObjectCheck = DateTime.Now; public OnlinePlayerManager(Framework framework, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager, PlayerManager playerManager) @@ -58,7 +58,7 @@ public class OnlinePlayerManager : IDisposable private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e) { - var visiblePlayer = _onlineCachedPlayers.SingleOrDefault(c => c.IsVisible && c.PlayerNameHash == e.CharacterNameHash); + var visiblePlayer = _onlineCachedPlayers.Select(v => v.Value).SingleOrDefault(c => c.IsVisible && c.PlayerNameHash == e.CharacterNameHash); if (visiblePlayer != null) { Logger.Debug("Received data and applying to " + e.CharacterNameHash); @@ -99,7 +99,15 @@ public class OnlinePlayerManager : IDisposable private void IpcManagerOnPenumbraDisposed() { - _onlineCachedPlayers.ForEach(p => p.DisposePlayer()); + DisposeAllPlayers(); + } + + private void DisposeAllPlayers() + { + foreach (var entry in _onlineCachedPlayers) + { + entry.Value.DisposePlayer(); + } } private void ApiControllerOnDisconnected() @@ -111,7 +119,10 @@ public class OnlinePlayerManager : IDisposable public void AddInitialPairs(List apiTaskResult) { _onlineCachedPlayers.Clear(); - _onlineCachedPlayers.AddRange(apiTaskResult.Select(CreateCachedPlayer)); + foreach (var result in apiTaskResult) + { + _onlineCachedPlayers.TryAdd(result, CreateCachedPlayer(result)); + } Logger.Verbose("Online and paired users: " + string.Join(Environment.NewLine, _onlineCachedPlayers)); } @@ -138,7 +149,7 @@ public class OnlinePlayerManager : IDisposable private void RestoreAllCharacters() { - _onlineCachedPlayers.ForEach(p => p.DisposePlayer()); + DisposeAllPlayers(); _onlineCachedPlayers.Clear(); } @@ -171,19 +182,23 @@ public class OnlinePlayerManager : IDisposable private void AddPlayer(string characterNameHash) { - if (_onlineCachedPlayers.Any(p => p.PlayerNameHash == characterNameHash)) + if (_onlineCachedPlayers.ContainsKey(characterNameHash)) { PushCharacterData(new List() { characterNameHash }); - _playerTokenDisposal.TryGetValue(_onlineCachedPlayers.Single(p => p.PlayerNameHash == characterNameHash), out var cancellationTokenSource); + _playerTokenDisposal.TryGetValue(_onlineCachedPlayers[characterNameHash], out var cancellationTokenSource); cancellationTokenSource?.Cancel(); return; } - _onlineCachedPlayers.Add(CreateCachedPlayer(characterNameHash)); + _onlineCachedPlayers.TryAdd(characterNameHash, CreateCachedPlayer(characterNameHash)); } private void RemovePlayer(string characterHash) { - var cachedPlayer = _onlineCachedPlayers.First(p => p.PlayerNameHash == characterHash); + if (!_onlineCachedPlayers.TryGetValue(characterHash, out var cachedPlayer)) + { + return; + } + if (_dalamudUtil.IsInGpose) { _playerTokenDisposal.TryGetValue(cachedPlayer, out var cancellationTokenSource); @@ -202,14 +217,14 @@ public class OnlinePlayerManager : IDisposable } cachedPlayer.DisposePlayer(); - _onlineCachedPlayers.RemoveAll(c => c.PlayerNameHash == cachedPlayer.PlayerNameHash); + _onlineCachedPlayers.TryRemove(cachedPlayer.PlayerNameHash, out _); }, token); return; } cachedPlayer.DisposePlayer(); - _onlineCachedPlayers.RemoveAll(c => c.PlayerNameHash == cachedPlayer.PlayerNameHash); + _onlineCachedPlayers.TryRemove(characterHash, out _); } private void FrameworkOnUpdate(Framework framework) @@ -222,8 +237,7 @@ public class OnlinePlayerManager : IDisposable foreach (var pChar in playerCharacters) { var hashedName = Crypto.GetHash256(pChar); - var existingCachedPlayer = _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName && !string.IsNullOrEmpty(p.PlayerName)); - if (existingCachedPlayer != null) + if (_onlineCachedPlayers.TryGetValue(hashedName, out var existingCachedPlayer) && !string.IsNullOrEmpty(existingCachedPlayer.PlayerName)) { existingCachedPlayer.IsVisible = true; continue; @@ -233,10 +247,14 @@ public class OnlinePlayerManager : IDisposable { _temporaryStoredCharacterCache.Remove(hashedName); } - _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar, cache); + + if (_onlineCachedPlayers.TryGetValue(hashedName, out var playerToInit)) + { + playerToInit.InitializePlayer(pChar, cache); + } } - var newlyVisiblePlayers = _onlineCachedPlayers + var newlyVisiblePlayers = _onlineCachedPlayers.Values .Where(p => p.PlayerCharacter != null && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash) .ToList(); if (newlyVisiblePlayers.Any()) From c2576a4bb6593ac55f7e172ae54e4507f40b2a91 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 7 Sep 2022 21:20:04 +0200 Subject: [PATCH 18/28] change queue to concurrentqueue --- MareSynchronos/Managers/IpcManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index b436d53..9207b60 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -7,6 +7,7 @@ using MareSynchronos.Utils; using MareSynchronos.WebAPI; using Lumina.Excel.GeneratedSheets; using Action = System.Action; +using System.Collections.Concurrent; namespace MareSynchronos.Managers { @@ -36,7 +37,7 @@ namespace MareSynchronos.Managers _penumbraSetTemporaryMod; private readonly ICallGateSubscriber _penumbraGameObjectResourcePathResolved; private readonly DalamudUtil _dalamudUtil; - private readonly Queue actionQueue = new(); + private readonly ConcurrentQueue actionQueue = new(); public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) { From 5b97d49af7939d751c3152989aecb5bea2fa5e47 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 7 Sep 2022 22:13:28 +0200 Subject: [PATCH 19/28] fix errors after merge --- MareSynchronos/Managers/CachedPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 80b296b..75db5ce 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -287,7 +287,7 @@ public class CachedPlayer { Thread.Sleep(tick); totalWait += tick; - newPet = _dalamudUtil.GetPet(PlayerCharacter.Address); + newPet = _dalamudUtil.GetPet(PlayerCharacter); } while (newPet == pet && totalWait < maxWait); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) From b2d6e23d276f4392797c11c550adf8c5af485eb9 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 7 Sep 2022 22:14:10 +0200 Subject: [PATCH 20/28] fix more merge errors --- MareSynchronos/UI/CompactUI.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index bbdcbad..04a23e1 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -55,23 +55,6 @@ namespace MareSynchronos.UI #endif Logger.Verbose("Creating " + nameof(CompactUi)); -#if DEBUG - string dateTime = "DEV VERSION"; - try - { - dateTime = VariousExtensions.GetLinkerTime(Assembly.GetCallingAssembly()).ToString("yyyyMMddHHmmss"); - } - catch (Exception ex) - { - Logger.Warn("Could not get assembly name"); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace); - } - this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; -#else - this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI"; -#endif - _windowSystem = windowSystem; _uiShared = uiShared; _configuration = configuration; From ca044364fc9b1708620ddb8c4cf427b8d708e24d Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 7 Sep 2022 22:15:52 +0200 Subject: [PATCH 21/28] fix another merge issue --- MareSynchronos/Managers/OnlinePlayerManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index c06b577..484af77 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -23,7 +23,7 @@ public class OnlinePlayerManager : IDisposable private readonly ConcurrentDictionary _temporaryStoredCharacterCache = new(); private readonly ConcurrentDictionary _playerTokenDisposal = new(); - private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != null) + private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero) .Select(p => p.PlayerNameHash).ToList(); private DateTime _lastPlayerObjectCheck = DateTime.Now; From e5f7c2f72daddd4082181c8b3721f574be5bab41 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Thu, 8 Sep 2022 11:48:20 +0200 Subject: [PATCH 22/28] add signalR logging and dalamudutil disposal, halt framework update between areas --- .../Managers/TransientResourceManager.cs | 3 +- MareSynchronos/Plugin.cs | 8 ++- MareSynchronos/Utils/DalamudUtil.cs | 33 ++++++--- MareSynchronos/Utils/Logger.cs | 72 ++++++++++++++++++- .../WebAPI/ApiController.Connectivity.cs | 5 ++ 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 848c42d..7bc3690 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -1,5 +1,4 @@ using MareSynchronos.API; -using MareSynchronos.FileCacheDB; using MareSynchronos.Models; using MareSynchronos.Utils; using System; @@ -42,7 +41,7 @@ namespace MareSynchronos.Managers { if (!dalamudUtil.IsGameObjectPresent(item.Key)) { - Logger.Debug("Object not present anymore: " + item.Key); + Logger.Debug("Object not present anymore: " + item.Key.ToString("X")); TransientResources.Remove(item.Key); } } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 753a233..d3b9ba8 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -14,6 +14,7 @@ using Dalamud.Interface.Windowing; using MareSynchronos.UI; using MareSynchronos.Utils; using System.Runtime.InteropServices; +using Dalamud.Game.ClientState.Conditions; namespace MareSynchronos { @@ -42,7 +43,7 @@ namespace MareSynchronos public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, - Framework framework, ObjectTable objectTable, ClientState clientState) + Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition) { Logger.Debug("Launching " + Name); PluginInterface = pluginInterface; @@ -60,7 +61,7 @@ namespace MareSynchronos new FileCacheContext().Dispose(); // make sure db is initialized I guess // those can be initialized outside of game login - _dalamudUtil = new DalamudUtil(clientState, objectTable, framework); + _dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition); _apiController = new ApiController(_configuration, _dalamudUtil); _ipcManager = new IpcManager(PluginInterface, _dalamudUtil); @@ -124,7 +125,7 @@ namespace MareSynchronos _commandManager.RemoveHandler(CommandName); _dalamudUtil.LogIn -= DalamudUtilOnLogIn; _dalamudUtil.LogOut -= DalamudUtilOnLogOut; - + _uiSharedComponent.Dispose(); _settingsUi?.Dispose(); _introUi?.Dispose(); @@ -136,6 +137,7 @@ namespace MareSynchronos _playerManager?.Dispose(); _characterCacheManager?.Dispose(); _transientResourceManager?.Dispose(); + _dalamudUtil.Dispose(); Logger.Debug("Shut down"); } diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index e31e557..362d116 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using Dalamud.Game; using Dalamud.Game.ClientState; +using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; using FFXIVClientStructs.FFXIV.Client.Game.Character; @@ -24,6 +25,8 @@ namespace MareSynchronos.Utils private readonly ClientState _clientState; private readonly ObjectTable _objectTable; private readonly Framework _framework; + private readonly Condition _condition; + public event LogIn? LogIn; public event LogOut? LogOut; public event FrameworkUpdate? FrameworkUpdate; @@ -45,11 +48,12 @@ namespace MareSynchronos.Utils return false; } - public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework) + public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition) { _clientState = clientState; _objectTable = objectTable; _framework = framework; + _condition = condition; _clientState.Login += ClientStateOnLogin; _clientState.Logout += ClientStateOnLogout; _framework.Update += FrameworkOnUpdate; @@ -62,11 +66,16 @@ namespace MareSynchronos.Utils private void FrameworkOnUpdate(Framework framework) { - foreach (FrameworkUpdate frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) + if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]) + { + return; + } + + foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) { try { - frameworkInvocation.Invoke(); + frameworkInvocation?.Invoke(); } catch (Exception ex) { @@ -75,15 +84,23 @@ namespace MareSynchronos.Utils } } - classJobId = _clientState.LocalPlayer.ClassJob.Id; - ClassJobChanged?.Invoke(); + if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; + if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid()) + { + var newclassJobId = _clientState.LocalPlayer.ClassJob.Id; - if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(0.25)) return; - foreach (FrameworkUpdate frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) + if (classJobId != newclassJobId) + { + classJobId = newclassJobId; + ClassJobChanged?.Invoke(); + } + } + + foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) { try { - frameworkInvocation.Invoke(); + frameworkInvocation?.Invoke(); } catch (Exception ex) { diff --git a/MareSynchronos/Utils/Logger.cs b/MareSynchronos/Utils/Logger.cs index 0e79f43..4785077 100644 --- a/MareSynchronos/Utils/Logger.cs +++ b/MareSynchronos/Utils/Logger.cs @@ -1,11 +1,37 @@ -using System.Diagnostics; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; using Dalamud.Logging; using Dalamud.Utility; +using Microsoft.Extensions.Logging; namespace MareSynchronos.Utils { - internal class Logger + [ProviderAlias("Dalamud")] + public class DalamudLoggingProvider : ILoggerProvider { + private readonly ConcurrentDictionary _loggers = + new(StringComparer.OrdinalIgnoreCase); + + public DalamudLoggingProvider() + { + } + + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName)); + } + + public void Dispose() + { + _loggers.Clear(); + } + } + + internal class Logger : ILogger + { + private readonly string name; + public static void Info(string info) { var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; @@ -40,5 +66,47 @@ namespace MareSynchronos.Utils PluginLog.Verbose($"[{caller}] {verbose}"); #endif } + + public Logger(string name) + { + this.name = name; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) return; + + switch (logLevel) + { + case LogLevel.Debug: + PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Error: + case LogLevel.Critical: + PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Information: + PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Warning: + PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Trace: + default: +#if DEBUG + PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}"); +#else + PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}"); +#endif + break; + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public IDisposable BeginScope(TState state) => default!; } } diff --git a/MareSynchronos/WebAPI/ApiController.Connectivity.cs b/MareSynchronos/WebAPI/ApiController.Connectivity.cs index 491299b..c6f722b 100644 --- a/MareSynchronos/WebAPI/ApiController.Connectivity.cs +++ b/MareSynchronos/WebAPI/ApiController.Connectivity.cs @@ -11,6 +11,7 @@ using MareSynchronos.WebAPI.Utils; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; namespace MareSynchronos.WebAPI { @@ -304,6 +305,10 @@ namespace MareSynchronos.WebAPI options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; }) .WithAutomaticReconnect(new ForeverRetryPolicy()) + .ConfigureLogging(a => { + a.ClearProviders().AddProvider(new DalamudLoggingProvider()); + a.SetMinimumLevel(LogLevel.Trace); + }) .Build(); } From 3decdd55e9faa9243766c9041aab53672bcbd868 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 14:21:20 +0200 Subject: [PATCH 23/28] stop processing framework queue while in gpose --- MareSynchronos/Managers/CachedPlayer.cs | 10 -------- .../Managers/OnlinePlayerManager.cs | 24 ------------------- MareSynchronos/Managers/PlayerManager.cs | 2 -- MareSynchronos/Utils/DalamudUtil.cs | 2 +- 4 files changed, 1 insertion(+), 37 deletions(-) diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 75db5ce..063d05f 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -163,16 +163,6 @@ public class CachedPlayer ApplyBaseData(moddedPaths); } - if (_dalamudUtil.IsInGpose) - { - Logger.Verbose("Player is in GPose, waiting"); - while (_dalamudUtil.IsInGpose) - { - await Task.Delay(TimeSpan.FromSeconds(0.5)); - downloadToken.ThrowIfCancellationRequested(); - } - } - foreach (var kind in objectKind) { ApplyCustomizationData(kind); diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index 484af77..d2671f8 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -198,30 +198,6 @@ public class OnlinePlayerManager : IDisposable return; } - if (_dalamudUtil.IsInGpose) - { - _playerTokenDisposal.TryGetValue(cachedPlayer, out var cancellationTokenSource); - cancellationTokenSource?.Cancel(); - cachedPlayer.IsVisible = false; - _playerTokenDisposal[cachedPlayer] = new CancellationTokenSource(); - cancellationTokenSource = _playerTokenDisposal[cachedPlayer]; - var token = cancellationTokenSource.Token; - Task.Run(async () => - { - Logger.Verbose("Cannot dispose Player, in GPose"); - while (_dalamudUtil.IsInGpose) - { - await Task.Delay(TimeSpan.FromSeconds(0.5)); - if (token.IsCancellationRequested) return; - } - - cachedPlayer.DisposePlayer(); - _onlineCachedPlayers.TryRemove(characterHash, out _); - }, token); - - return; - } - cachedPlayer.DisposePlayer(); _onlineCachedPlayers.TryRemove(characterHash, out _); } diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index d14e8c6..ad37f3f 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -165,8 +165,6 @@ namespace MareSynchronos.Managers private void OnPlayerOrAttachedObjectsChanged() { - if (_dalamudUtil.IsInGpose) return; - var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList(); foreach (var unprocessedObject in unprocessedObjects) { diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 362d116..24409a7 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -66,7 +66,7 @@ namespace MareSynchronos.Utils private void FrameworkOnUpdate(Framework framework) { - if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]) + if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose) { return; } From b9d119acf112e328d0112d58abf0252b68691a5b Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 14:38:06 +0200 Subject: [PATCH 24/28] forcefully stick download ui to main window --- MareSynchronos/UI/DownloadUi.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MareSynchronos/UI/DownloadUi.cs b/MareSynchronos/UI/DownloadUi.cs index 9e9f05f..ca91914 100644 --- a/MareSynchronos/UI/DownloadUi.cs +++ b/MareSynchronos/UI/DownloadUi.cs @@ -44,6 +44,8 @@ public class DownloadUi : Window, IDisposable Flags |= ImGuiWindowFlags.NoTitleBar; Flags |= ImGuiWindowFlags.NoDecoration; + ForceMainWindow = true; + windowSystem.AddWindow(this); IsOpen = true; } From ce1a1187f0297dcc968250eb73163404697c369f Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 14:53:59 +0200 Subject: [PATCH 25/28] minor fixes --- .../Factories/CharacterDataFactory.cs | 2 +- MareSynchronos/Managers/CachedPlayer.cs | 24 ++++++++++++------- MareSynchronos/Managers/IpcManager.cs | 2 ++ MareSynchronos/Managers/PlayerManager.cs | 2 +- MareSynchronos/Utils/DalamudUtil.cs | 8 +++---- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 90be2f5..91705c0 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -239,7 +239,7 @@ public class CharacterDataFactory Thread.Sleep(50); } - _dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer); + _dalamudUtil.WaitWhileCharacterIsDrawing(objectKind.ToString(), charaPointer); Stopwatch st = Stopwatch.StartNew(); diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 063d05f..f64364c 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -165,7 +165,7 @@ public class CachedPlayer foreach (var kind in objectKind) { - ApplyCustomizationData(kind); + ApplyCustomizationData(kind, downloadToken); } }, downloadToken).ContinueWith(task => { @@ -225,14 +225,15 @@ public class CachedPlayer _ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData); } - private unsafe void ApplyCustomizationData(ObjectKind objectKind) + private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct) { if (PlayerCharacter == IntPtr.Zero) return; _cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData); if (objectKind == ObjectKind.Player) { - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, ct); + ct.ThrowIfCancellationRequested(); RequestedPenumbraRedraw = true; Logger.Debug( $"Request Redraw for {PlayerName}"); @@ -251,7 +252,8 @@ public class CachedPlayer if (minionOrMount != null) { Logger.Debug($"Request Redraw for Minion/Mount"); - _dalamudUtil.WaitWhileCharacterIsDrawing((IntPtr)minionOrMount); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, ct); + ct.ThrowIfCancellationRequested(); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { _ipcManager.GlamourerApplyAll(glamourerData, obj: (IntPtr)minionOrMount); @@ -296,7 +298,8 @@ public class CachedPlayer if (companion != IntPtr.Zero) { Logger.Debug("Request Redraw for Companion"); - _dalamudUtil.WaitWhileCharacterIsDrawing(companion); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, ct); + ct.ThrowIfCancellationRequested(); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { _ipcManager.GlamourerApplyAll(glamourerData, companion); @@ -441,14 +444,16 @@ public class CachedPlayer _penumbraRedrawEventTask = Task.Run(() => { PlayerCharacter = address; - using var cts = new CancellationTokenSource(); + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(5)); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, cts.Token); + cts.Dispose(); + cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter, cts.Token); - if (RequestedPenumbraRedraw == false) { Logger.Debug("Unauthorized character change detected"); - ApplyCustomizationData(ObjectKind.Player); + ApplyCustomizationData(ObjectKind.Player, cts.Token); } else { @@ -456,6 +461,7 @@ public class CachedPlayer Logger.Debug( $"Penumbra Redraw done for {PlayerName}"); } + cts.Dispose(); }); } diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index dcbc3f7..39cd723 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -142,10 +142,12 @@ namespace MareSynchronos.Managers int totalSleepTime = 0; while (actionQueue.Count > 0 && totalSleepTime < 2000) { + Logger.Verbose("Waiting for actionqueue to clear..."); System.Threading.Thread.Sleep(16); totalSleepTime += 16; } + Logger.Verbose("Action queue clear or not, disposing"); _dalamudUtil.FrameworkUpdate -= HandleActionQueue; actionQueue.Clear(); diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index ad37f3f..cdca271 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -200,7 +200,7 @@ namespace MareSynchronos.Managers { foreach(var item in unprocessedObjects) { - _dalamudUtil.WaitWhileCharacterIsDrawing(item.Address, token); + _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, token); } CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token)); diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 24409a7..070a2ad 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -189,18 +189,18 @@ namespace MareSynchronos.Utils return null; } - public unsafe void WaitWhileCharacterIsDrawing(IntPtr characterAddress, CancellationToken? ct = null) + public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, CancellationToken? ct = null) { if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return; var obj = (GameObject*)characterAddress; - const int maxWaitTime = 10000; + const int maxWaitTime = 5000; const int tick = 250; int curWaitTime = 0; // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < maxWaitTime) // 0b100000000000 is "still rendering" or something { - Logger.Verbose("Waiting for character to finish drawing"); + Logger.Verbose($"Waiting for {name} to finish drawing"); curWaitTime += tick; Thread.Sleep(tick); } @@ -210,7 +210,7 @@ namespace MareSynchronos.Utils Thread.Sleep(tick); } - public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing(_clientState.LocalPlayer?.Address ?? IntPtr.Zero, token); + public void WaitWhileSelfIsDrawing(CancellationToken? token) => WaitWhileCharacterIsDrawing("self", _clientState.LocalPlayer?.Address ?? IntPtr.Zero, token); public void Dispose() { From 6bf16e1d56aa30dee7398fb0e8989514db416678 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 17:42:21 +0200 Subject: [PATCH 26/28] minor fixes --- MareSynchronos/WebAPI/ApIController.Functions.Files.cs | 2 ++ MareSynchronos/WebAPI/ApiController.Connectivity.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 8b0532b..8d6c88f 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -76,6 +76,8 @@ namespace MareSynchronos.WebAPI List downloadFileInfoFromService = new List(); downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); + Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); + CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) .Where(d => d.CanBeTransferred).ToList(); diff --git a/MareSynchronos/WebAPI/ApiController.Connectivity.cs b/MareSynchronos/WebAPI/ApiController.Connectivity.cs index c6f722b..07b6706 100644 --- a/MareSynchronos/WebAPI/ApiController.Connectivity.cs +++ b/MareSynchronos/WebAPI/ApiController.Connectivity.cs @@ -342,7 +342,7 @@ namespace MareSynchronos.WebAPI _uploadCancellationTokenSource?.Cancel(); Logger.Info("Stopping existing connection"); _mareHub.Closed -= MareHubOnClosed; - _mareHub.Reconnecting += MareHubOnReconnecting; + _mareHub.Reconnecting -= MareHubOnReconnecting; await _mareHub.StopAsync(token); await _mareHub.DisposeAsync(); CurrentUploads.Clear(); From f785288971a33eb817e9cdfb4d572bbb65e507e5 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 21:02:25 +0200 Subject: [PATCH 27/28] move building character data to main framework thread --- MareSynchronos/Factories/CharacterDataFactory.cs | 13 ++++++++++--- MareSynchronos/Managers/PlayerManager.cs | 2 +- MareSynchronos/Utils/DalamudUtil.cs | 6 ++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 91705c0..8f5a074 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; +using Dalamud.Game; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; @@ -33,7 +35,12 @@ public class CharacterDataFactory this.transientResourceManager = transientResourceManager; } - public unsafe CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token) + private unsafe bool CheckForPointer(IntPtr playerPointer) + { + return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null; + } + + public async Task BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token) { if (!_ipcManager.Initialized) { @@ -43,7 +50,7 @@ public class CharacterDataFactory bool pointerIsZero = true; try { - pointerIsZero = playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null; + pointerIsZero = CheckForPointer(playerPointer); } catch (Exception ex) { @@ -65,7 +72,7 @@ public class CharacterDataFactory try { - return CreateCharacterData(previousData, objectKind, playerPointer, token); + return await _dalamudUtil.RunOnFrameworkThread(() => CreateCharacterData(previousData, objectKind, playerPointer, token)); } catch (OperationCanceledException) { diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index cdca271..c14d834 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -119,7 +119,7 @@ namespace MareSynchronos.Managers foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList()) { Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind); - PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address, token); + PermanentDataCache = await _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address, token); if (!token.IsCancellationRequested) { unprocessedObject.HasUnprocessedUpdate = false; diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 070a2ad..c37c6ac 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.ClientState.Conditions; @@ -189,6 +190,11 @@ namespace MareSynchronos.Utils return null; } + public async Task RunOnFrameworkThread(Func func) + { + return await _framework.RunOnFrameworkThread(func); + } + public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, CancellationToken? ct = null) { if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return; From 01184f543f05ba1d62af46e5d1fcb3e33d2ab2dd Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 9 Sep 2022 21:22:25 +0200 Subject: [PATCH 28/28] revert change generating character data on main thread --- MareSynchronos/Factories/CharacterDataFactory.cs | 4 ++-- MareSynchronos/Managers/PlayerManager.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index 8f5a074..8f2e049 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -40,7 +40,7 @@ public class CharacterDataFactory return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null; } - public async Task BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token) + public CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token) { if (!_ipcManager.Initialized) { @@ -72,7 +72,7 @@ public class CharacterDataFactory try { - return await _dalamudUtil.RunOnFrameworkThread(() => CreateCharacterData(previousData, objectKind, playerPointer, token)); + return CreateCharacterData(previousData, objectKind, playerPointer, token); } catch (OperationCanceledException) { diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index c14d834..cdca271 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -119,7 +119,7 @@ namespace MareSynchronos.Managers foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList()) { Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind); - PermanentDataCache = await _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address, token); + PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address, token); if (!token.IsCancellationRequested) { unprocessedObject.HasUnprocessedUpdate = false;