From 8459fe8f256bc9bea054afae4fe4504ab02df466 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 15 Aug 2022 17:36:43 +0200 Subject: [PATCH] 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;