add delayedframework update to dalamudutil, rework to concurrent collections

This commit is contained in:
Stanley Dimant
2022-09-07 22:03:17 +02:00
parent 35ebaed80c
commit 920090c3b1
6 changed files with 128 additions and 85 deletions

View File

@@ -226,7 +226,7 @@ public class CharacterDataFactory
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(chara);
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)

View File

@@ -47,7 +47,7 @@ public class CachedPlayer
private string _originalGlamourerData = string.Empty;
public Dalamud.Game.ClientState.Objects.Types.Character? PlayerCharacter { get; set; }
public IntPtr PlayerCharacter { get; set; } = IntPtr.Zero;
public string? PlayerName { get; private set; }
@@ -226,27 +226,27 @@ public class CachedPlayer
private unsafe void ApplyCustomizationData(ObjectKind objectKind)
{
if (PlayerCharacter is null) return;
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
if (objectKind == ObjectKind.Player)
{
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address);
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter.Address);
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter);
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter.Address);
_ipcManager.PenumbraRedraw(PlayerCharacter);
}
}
else if (objectKind == ObjectKind.MinionOrMount)
{
var minionOrMount = ((Character*)PlayerCharacter.Address)->CompanionObject;
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
@@ -262,7 +262,7 @@ public class CachedPlayer
}
else if (objectKind == ObjectKind.Pet)
{
var pet = _dalamudUtil.GetPet(PlayerCharacter.Address);
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Pet");
@@ -278,7 +278,7 @@ public class CachedPlayer
}
else if (objectKind == ObjectKind.Companion)
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter.Address);
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
@@ -296,7 +296,7 @@ public class CachedPlayer
private unsafe void RevertCustomizationData(ObjectKind objectKind)
{
if (PlayerCharacter is null) return;
if (PlayerCharacter == IntPtr.Zero) return;
if (objectKind == ObjectKind.Player)
{
@@ -307,12 +307,12 @@ public class CachedPlayer
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter.Address);
_ipcManager.PenumbraRedraw(PlayerCharacter);
}
}
else if (objectKind == ObjectKind.MinionOrMount)
{
var minionOrMount = ((Character*)PlayerCharacter.Address)->CompanionObject;
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
_ipcManager.PenumbraRedraw((IntPtr)minionOrMount);
@@ -320,7 +320,7 @@ public class CachedPlayer
}
else if (objectKind == ObjectKind.Pet)
{
var pet = _dalamudUtil.GetPet(PlayerCharacter.Address);
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
_ipcManager.PenumbraRedraw(pet);
@@ -328,7 +328,7 @@ public class CachedPlayer
}
else if (objectKind == ObjectKind.Companion)
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter.Address);
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
_ipcManager.PenumbraRedraw(companion);
@@ -345,10 +345,10 @@ public class CachedPlayer
try
{
Logger.Verbose("Restoring state for " + PlayerName);
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
if (PlayerCharacter != null && PlayerCharacter.IsValid())
if (PlayerCharacter != IntPtr.Zero)
{
foreach (var item in _cachedData.FileReplacements)
{
@@ -367,18 +367,18 @@ public class CachedPlayer
{
_cachedData = new();
PlayerName = string.Empty;
PlayerCharacter = null;
PlayerCharacter = IntPtr.Zero;
IsVisible = false;
}
}
public void InitializePlayer(PlayerCharacter character, CharacterCacheDto? cache)
public void InitializePlayer(IntPtr character, string name, CharacterCacheDto? cache)
{
Logger.Debug("Initializing Player " + this + " has cache: " + (cache != null));
IsVisible = true;
PlayerName = character.Name.ToString();
PlayerName = name;
PlayerCharacter = character;
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
@@ -390,12 +390,12 @@ public class CachedPlayer
}
}
private void DalamudUtilOnFrameworkUpdate()
private void DalamudUtilOnDelayedFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
PlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!);
if (PlayerCharacter == null)
PlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero;
if (PlayerCharacter == IntPtr.Zero)
{
DisposePlayer();
return;
@@ -412,7 +412,7 @@ public class CachedPlayer
public override string ToString()
{
return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != null);
return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != IntPtr.Zero);
}
private Task? _penumbraRedrawEventTask;
@@ -425,10 +425,10 @@ public class CachedPlayer
_penumbraRedrawEventTask = Task.Run(() =>
{
PlayerCharacter = player;
PlayerCharacter = address;
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address, cts.Token);
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter, cts.Token);
if (RequestedPenumbraRedraw == false)
{
@@ -448,10 +448,10 @@ public class CachedPlayer
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter is not null)
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter!);
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
}
}
}

View File

@@ -6,6 +6,7 @@ using Dalamud.Game.ClientState.Objects.Types;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using Action = System.Action;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers
{
@@ -33,7 +34,7 @@ namespace MareSynchronos.Managers
private readonly ICallGateSubscriber<string, string, Dictionary<string, string>, string, int, int>
_penumbraSetTemporaryMod;
private readonly DalamudUtil _dalamudUtil;
private readonly Queue<Action> actionQueue = new();
private readonly ConcurrentQueue<Action> actionQueue = new();
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
{
@@ -83,8 +84,9 @@ namespace MareSynchronos.Managers
private void HandleActionQueue()
{
while (actionQueue.TryDequeue(out var action))
if (actionQueue.TryDequeue(out var action))
{
if (action == null) return;
Logger.Debug("Execution action in queue: " + action.Method);
action();
}
@@ -145,37 +147,50 @@ namespace MareSynchronos.Managers
});
}
public void GlamourerApplyOnlyEquipment(string customization, GameObject character)
public void GlamourerApplyOnlyEquipment(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
{
Logger.Verbose("Glamourer apply only equipment to " + character);
_glamourerApplyOnlyEquipment!.InvokeAction(customization, character);
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
Logger.Verbose("Glamourer apply only equipment to " + character.ToString("X"));
_glamourerApplyOnlyEquipment!.InvokeAction(customization, gameObj);
}
});
}
public void GlamourerApplyOnlyCustomization(string customization, GameObject character)
public void GlamourerApplyOnlyCustomization(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
{
Logger.Verbose("Glamourer apply only customization to " + character);
_glamourerApplyOnlyCustomization!.InvokeAction(customization, character);
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
Logger.Verbose("Glamourer apply only customization to " + character.ToString("X"));
_glamourerApplyOnlyCustomization!.InvokeAction(customization, gameObj);
}
});
}
public string GlamourerGetCharacterCustomization(GameObject character)
public string GlamourerGetCharacterCustomization(IntPtr character)
{
if (!CheckGlamourerApi()) return string.Empty;
try
{
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(character);
byte[] bytes = Convert.FromBase64String(glamourerString);
// ignore transparency
bytes[88] = 128;
bytes[89] = 63;
return Convert.ToBase64String(bytes);
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(gameObj);
byte[] bytes = Convert.FromBase64String(glamourerString);
// ignore transparency
bytes[88] = 128;
bytes[89] = 63;
return Convert.ToBase64String(bytes);
}
return string.Empty;
}
catch
{
@@ -189,14 +204,6 @@ namespace MareSynchronos.Managers
actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
}
public string PenumbraCreateTemporaryCollection(string characterName)
{
if (!CheckPenumbraApi()) return string.Empty;
Logger.Verbose("Creating temp collection for " + characterName);
var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true);
return ret.Item2;
}
public string PenumbraGetMetaManipulations()
{
if (!CheckPenumbraApi()) return string.Empty;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -19,11 +20,11 @@ public class OnlinePlayerManager : IDisposable
private readonly Framework _framework;
private readonly IpcManager _ipcManager;
private readonly PlayerManager _playerManager;
private readonly List<CachedPlayer> _onlineCachedPlayers = new();
private readonly Dictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new();
private readonly Dictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new();
private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new();
private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new();
private readonly ConcurrentDictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new();
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Where(p => p.PlayerCharacter != null)
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != null)
.Select(p => p.PlayerNameHash).ToList();
private DateTime _lastPlayerObjectCheck = DateTime.Now;
@@ -58,8 +59,7 @@ public class OnlinePlayerManager : IDisposable
private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e)
{
var visiblePlayer = _onlineCachedPlayers.SingleOrDefault(c => c.IsVisible && c.PlayerNameHash == e.CharacterNameHash);
if (visiblePlayer != null)
if (_onlineCachedPlayers.TryGetValue(e.CharacterNameHash, out var visiblePlayer) && visiblePlayer.IsVisible)
{
Logger.Debug("Received data and applying to " + e.CharacterNameHash);
visiblePlayer.ApplyCharacterData(e.CharacterData);
@@ -99,7 +99,15 @@ public class OnlinePlayerManager : IDisposable
private void IpcManagerOnPenumbraDisposed()
{
_onlineCachedPlayers.ForEach(p => p.DisposePlayer());
DisposePlayers();
}
private void DisposePlayers()
{
foreach (var kvp in _onlineCachedPlayers)
{
kvp.Value.DisposePlayer();
}
}
private void ApiControllerOnDisconnected()
@@ -111,8 +119,11 @@ public class OnlinePlayerManager : IDisposable
public void AddInitialPairs(List<string> apiTaskResult)
{
_onlineCachedPlayers.Clear();
_onlineCachedPlayers.AddRange(apiTaskResult.Select(CreateCachedPlayer));
Logger.Verbose("Online and paired users: " + string.Join(Environment.NewLine, _onlineCachedPlayers));
foreach (var hash in apiTaskResult)
{
_onlineCachedPlayers.TryAdd(hash, CreateCachedPlayer(hash));
}
Logger.Verbose("Online and paired users: " + string.Join(Environment.NewLine, _onlineCachedPlayers.Select(k => k.Key)));
}
public void Dispose()
@@ -138,7 +149,7 @@ public class OnlinePlayerManager : IDisposable
private void RestoreAllCharacters()
{
_onlineCachedPlayers.ForEach(p => p.DisposePlayer());
DisposePlayers();
_onlineCachedPlayers.Clear();
}
@@ -171,19 +182,23 @@ public class OnlinePlayerManager : IDisposable
private void AddPlayer(string characterNameHash)
{
if (_onlineCachedPlayers.Any(p => p.PlayerNameHash == characterNameHash))
if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer))
{
PushCharacterData(new List<string>() { characterNameHash });
_playerTokenDisposal.TryGetValue(_onlineCachedPlayers.Single(p => p.PlayerNameHash == characterNameHash), out var cancellationTokenSource);
_playerTokenDisposal.TryGetValue(cachedPlayer, 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(characterHash, out _);
}, token);
return;
}
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.RemoveAll(c => c.PlayerNameHash == cachedPlayer.PlayerNameHash);
_onlineCachedPlayers.TryRemove(characterHash, out _);
}
private void FrameworkOnUpdate(Framework framework)
@@ -222,22 +237,21 @@ 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 existingPlayer) && !string.IsNullOrEmpty(existingPlayer.PlayerName))
{
existingCachedPlayer.IsVisible = true;
existingPlayer.IsVisible = true;
continue;
}
if (_temporaryStoredCharacterCache.TryGetValue(hashedName, out var cache))
if (existingPlayer != null)
{
_temporaryStoredCharacterCache.Remove(hashedName);
_temporaryStoredCharacterCache.TryRemove(hashedName, out var cache);
existingPlayer.InitializePlayer(pChar.Address, pChar.Name.ToString(), cache);
}
_onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar, cache);
}
var newlyVisiblePlayers = _onlineCachedPlayers
.Where(p => p.PlayerCharacter != null && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash)
var newlyVisiblePlayers = _onlineCachedPlayers.Select(v => v.Value)
.Where(p => p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash)
.ToList();
if (newlyVisiblePlayers.Any())
{

View File

@@ -26,7 +26,6 @@ namespace MareSynchronos.Managers
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
private CancellationTokenSource? _playerChangedCts = new();
private DateTime _lastPlayerObjectCheck;
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
@@ -42,7 +41,7 @@ namespace MareSynchronos.Managers
_apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiController_Disconnected;
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
if (_apiController.IsConnected)
@@ -67,22 +66,18 @@ namespace MareSynchronos.Managers
_apiController.Disconnected -= ApiController_Disconnected;
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
}
private unsafe void DalamudUtilOnFrameworkUpdate()
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (playerRelatedObjects.Any(c => c.HasUnprocessedUpdate && !c.IsProcessing))
{
OnPlayerOrAttachedObjectsChanged();
}
_lastPlayerObjectCheck = DateTime.Now;
}
private void ApiControllerOnConnected()

View File

@@ -6,7 +6,6 @@ using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
@@ -27,6 +26,8 @@ namespace MareSynchronos.Utils
public event LogIn? LogIn;
public event LogOut? LogOut;
public event FrameworkUpdate? FrameworkUpdate;
public event FrameworkUpdate? DelayedFrameworkUpdate;
private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework)
{
@@ -44,7 +45,33 @@ namespace MareSynchronos.Utils
private void FrameworkOnUpdate(Framework framework)
{
FrameworkUpdate?.Invoke();
foreach (FrameworkUpdate frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
try
{
frameworkInvocation.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(0.25)) return;
foreach (FrameworkUpdate frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty<FrameworkUpdate>()).Cast<FrameworkUpdate>())
{
try
{
frameworkInvocation.Invoke();
}
catch (Exception ex)
{
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
}
_delayedFrameworkUpdateCheck = DateTime.Now;
}
private void ClientStateOnLogout(object? sender, EventArgs e)