rework a lot of stuff: downloads, how to watch a player

This commit is contained in:
Stanley Dimant
2022-06-30 13:24:35 +02:00
parent eb39429777
commit 3618540402
18 changed files with 319 additions and 233 deletions

View File

@@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Logging;
using MareSynchronos.API;
using MareSynchronos.FileCacheDB;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using MareSynchronos.WebAPI.Utils;
using Penumbra.GameData.Structs;
namespace MareSynchronos.Managers;
public class CachedPlayer
{
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private bool _isVisible;
public CachedPlayer(string nameHash, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil)
{
PlayerNameHash = nameHash;
_ipcManager = ipcManager;
_apiController = apiController;
_dalamudUtil = dalamudUtil;
}
public bool IsVisible
{
get => _isVisible;
set
{
WasVisible = _isVisible;
_isVisible = value;
}
}
private CancellationTokenSource? _downloadCancellationTokenSource;
private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty;
public PlayerCharacter? PlayerCharacter { get; set; }
public string? PlayerName { get; private set; }
public string PlayerNameHash { get; }
private string _lastAppliedEquipmentHash = string.Empty;
public bool RequestedPenumbraRedraw { get; set; }
public bool WasVisible { get; private set; }
private readonly Dictionary<string, CharacterCacheDto> _cache = new();
private CharacterEquipment? _currentCharacterEquipment;
private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e)
{
if (string.IsNullOrEmpty(PlayerName) || e.CharacterNameHash != PlayerNameHash) return;
Logger.Debug("Received data for " + this);
Logger.Debug("Checking for files to download for player " + PlayerName);
Logger.Debug("Hash for data is " + e.CharacterData.Hash);
if (!_cache.ContainsKey(e.CharacterData.Hash))
{
Logger.Debug("Received total " + e.CharacterData.FileReplacements.Count + " file replacement data");
_cache[e.CharacterData.Hash] = e.CharacterData;
}
else
{
Logger.Debug("Had valid local cache for " + PlayerName);
}
_lastAppliedEquipmentHash = e.CharacterData.Hash;
DownloadAndApplyCharacter();
}
private void DownloadAndApplyCharacter()
{
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource = new CancellationTokenSource();
var downloadToken = _downloadCancellationTokenSource.Token;
Task.Run(async () =>
{
List<FileReplacementDto> toDownloadReplacements;
Dictionary<string, string> moddedPaths;
while ((toDownloadReplacements = TryCalculateModdedDictionary(_cache[_lastAppliedEquipmentHash], out moddedPaths)).Count > 0)
{
Logger.Debug("Downloading missing files for player " + PlayerName);
await _apiController.DownloadFiles(toDownloadReplacements, downloadToken);
if (downloadToken.IsCancellationRequested)
{
return;
}
}
ApplyCharacterData(_cache[_lastAppliedEquipmentHash], moddedPaths);
}, downloadToken);
}
private List<FileReplacementDto> TryCalculateModdedDictionary(CharacterCacheDto cache,
out Dictionary<string, string> moddedDictionary)
{
List<FileReplacementDto> missingFiles = new();
moddedDictionary = new Dictionary<string, string>();
try
{
using var db = new FileCacheContext();
foreach (var item in cache.FileReplacements)
{
foreach (var gamePath in item.GamePaths)
{
var fileCache = db.FileCaches.FirstOrDefault(f => f.Hash == item.Hash);
if (fileCache != null)
{
moddedDictionary.Add(gamePath, fileCache.Filepath);
}
else
{
missingFiles.Add(item);
}
}
}
}
catch (Exception ex)
{
PluginLog.Error(ex, "Something went wrong during calculation replacements");
}
Logger.Debug("ModdedPaths calculated, missing files: " + missingFiles.Count);
return missingFiles;
}
private void ApplyCharacterData(CharacterCacheDto cache, Dictionary<string, string> moddedPaths)
{
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
var tempCollection = _ipcManager.PenumbraCreateTemporaryCollection(PlayerName!);
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter!.Address);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
_ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, cache.ManipulationData);
_ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerName!);
}
public void DisposePlayer()
{
Logger.Debug("Disposing " + PlayerNameHash);
if (string.IsNullOrEmpty(PlayerName)) return;
try
{
Logger.Debug("Restoring state for " + PlayerName);
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_apiController.CharacterReceived -= ApiControllerOnCharacterReceived;
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
_downloadCancellationTokenSource = null;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
if (PlayerCharacter != null)
{
_ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerName);
_ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerName);
}
}
catch (Exception ex)
{
Logger.Warn(ex.Message + Environment.NewLine + ex.StackTrace);
}
finally
{
PlayerName = string.Empty;
PlayerCharacter = null;
IsVisible = false;
}
}
public void InitializePlayer(PlayerCharacter character)
{
IsVisible = true;
PlayerName = character.Name.ToString();
PlayerCharacter = character;
Logger.Debug("Initializing Player " + this);
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_apiController.CharacterReceived += ApiControllerOnCharacterReceived;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerName);
_currentCharacterEquipment = new CharacterEquipment(PlayerCharacter);
_lastPlayerObjectCheck = DateTime.Now;
}
private void DalamudUtilOnFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
PlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!);
if (PlayerCharacter == null)
{
DisposePlayer();
return;
}
if (!_currentCharacterEquipment!.CompareAndUpdate(PlayerCharacter))
{
OnPlayerChanged();
}
IsVisible = true;
}
public override string ToString()
{
return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != null);
}
private Task? _penumbraRedrawEventTask;
private void IpcManagerOnPenumbraRedrawEvent(object? sender, EventArgs e)
{
var player = _dalamudUtil.GetPlayerCharacterFromObjectTableByIndex((int)sender!);
if (player == null || player.Name.ToString() != PlayerName) return;
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() =>
{
PlayerCharacter = player;
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerCharacter.Address);
if (RequestedPenumbraRedraw == false && !string.IsNullOrEmpty(_lastAppliedEquipmentHash))
{
Logger.Warn("Unauthorized character change detected");
DownloadAndApplyCharacter();
}
else
{
RequestedPenumbraRedraw = false;
Logger.Debug(
$"Penumbra Redraw done for {PlayerName}");
}
});
}
private void OnPlayerChanged()
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
PlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!);
if (!RequestedPenumbraRedraw && PlayerCharacter is not null)
{
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerName!);
}
}
}

View File

@@ -19,8 +19,8 @@ namespace MareSynchronos.Managers
private FileSystemWatcher? _cacheDirWatcher;
private FileSystemWatcher? _penumbraDirWatcher;
private Task? _rescanTask;
private CancellationTokenSource? _rescanTaskCancellationTokenSource;
private CancellationTokenSource? _rescanTaskRunCancellationTokenSource;
private CancellationTokenSource _rescanTaskCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource _rescanTaskRunCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource? _scanCancellationTokenSource;
private Task? _scanTask;
public FileCacheManager(IpcManager ipcManager, Configuration pluginConfiguration)
@@ -94,7 +94,6 @@ namespace MareSynchronos.Managers
_penumbraDirWatcher = new FileSystemWatcher(_ipcManager.PenumbraModDirectory()!)
{
IncludeSubdirectories = true,
InternalBufferSize = 1048576
};
_penumbraDirWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
_penumbraDirWatcher.Deleted += OnModified;
@@ -108,8 +107,7 @@ namespace MareSynchronos.Managers
_cacheDirWatcher = new FileSystemWatcher(_pluginConfiguration.CacheFolder)
{
IncludeSubdirectories = true,
InternalBufferSize = 1048576
IncludeSubdirectories = false,
};
_cacheDirWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
_cacheDirWatcher.Deleted += OnModified;
@@ -150,7 +148,7 @@ namespace MareSynchronos.Managers
private void OnModified(object sender, FileSystemEventArgs e)
{
_modifiedFiles.Add(e.FullPath);
Task.Run(() => _ = RescanTask());
_ = StartRescan();
}
private void RecalculateFileCacheSize()
@@ -169,12 +167,12 @@ namespace MareSynchronos.Managers
}
}
public async Task RescanTask(bool force = false)
public async Task StartRescan(bool force = false)
{
_rescanTaskRunCancellationTokenSource?.Cancel();
_rescanTaskRunCancellationTokenSource.Cancel();
_rescanTaskRunCancellationTokenSource = new CancellationTokenSource();
var token = _rescanTaskRunCancellationTokenSource.Token;
if(!force)
if (!force)
await Task.Delay(TimeSpan.FromSeconds(1), token);
while ((!_rescanTask?.IsCompleted ?? false) && !token.IsCancellationRequested)
{
@@ -183,11 +181,10 @@ namespace MareSynchronos.Managers
if (token.IsCancellationRequested) return;
PluginLog.Debug("File changes detected, scanning the changes");
PluginLog.Debug("File changes detected, scanning the changes ");
if (!_modifiedFiles.Any()) return;
_rescanTaskCancellationTokenSource = new CancellationTokenSource();
_rescanTask = Task.Run(async () =>
{
var listCopy = _modifiedFiles.ToList();

View File

@@ -119,14 +119,14 @@ namespace MareSynchronos.Managers
public void GlamourerApplyOnlyEquipment(string customization, string characterName)
{
if (!CheckGlamourerApi()) return;
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
Logger.Debug("Glamourer apply only equipment to " + characterName);
_glamourerApplyOnlyEquipment!.InvokeAction(customization, characterName);
}
public void GlamourerApplyOnlyCustomization(string customization, string characterName)
{
if (!CheckGlamourerApi()) return;
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
Logger.Debug("Glamourer apply only customization to " + characterName);
_glamourerApplyOnlyCustomization!.InvokeAction(customization, characterName);
}

View File

@@ -3,25 +3,23 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Game;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class CachedPlayersManager : IDisposable
public class OnlinePlayerManager : IDisposable
{
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly Framework _framework;
private readonly IpcManager _ipcManager;
private readonly List<CachedPlayer> _onlineCachedPlayers = new();
private readonly List<string> _localVisiblePlayers = new();
private DateTime _lastPlayerObjectCheck = DateTime.Now;
public CachedPlayersManager(Framework framework, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager)
public OnlinePlayerManager(Framework framework, ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager)
{
Logger.Debug("Creating " + nameof(CachedPlayersManager));
Logger.Debug("Creating " + nameof(OnlinePlayerManager));
_framework = framework;
_apiController = apiController;
@@ -74,7 +72,7 @@ public class CachedPlayersManager : IDisposable
public void Dispose()
{
Logger.Debug("Disposing " + nameof(CachedPlayersManager));
Logger.Debug("Disposing " + nameof(OnlinePlayerManager));
RestoreAllCharacters();
@@ -142,9 +140,9 @@ public class CachedPlayersManager : IDisposable
private void RemovePlayer(string characterHash)
{
var cachedPlayer = _onlineCachedPlayers.Single(p => p.PlayerNameHash == characterHash);
var cachedPlayer = _onlineCachedPlayers.First(p => p.PlayerNameHash == characterHash);
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.Remove(cachedPlayer);
_onlineCachedPlayers.RemoveAll(c => c.PlayerNameHash == cachedPlayer.PlayerNameHash);
}
private void FrameworkOnUpdate(Framework framework)
@@ -153,26 +151,21 @@ public class CachedPlayersManager : IDisposable
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
_localVisiblePlayers.Clear();
var playerCharacters = _dalamudUtil.GetPlayerCharacters();
foreach (var pChar in playerCharacters)
{
var pObjName = pChar.Name.ToString();
_localVisiblePlayers.Add(pObjName);
var existingCachedPlayer = _onlineCachedPlayers.SingleOrDefault(p => p.PlayerName == pObjName);
var hashedName = Crypto.GetHash256(pChar);
var existingCachedPlayer = _onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName && !string.IsNullOrEmpty(p.PlayerName));
if (existingCachedPlayer != null)
{
existingCachedPlayer.IsVisible = true;
continue;
}
var hashedName = Crypto.GetHash256(pChar);
_onlineCachedPlayers.SingleOrDefault(p => p.PlayerNameHash == hashedName)?.InitializePlayer(pChar);
}
_onlineCachedPlayers.Where(p => !string.IsNullOrEmpty(p.PlayerName) && !_localVisiblePlayers.Contains(p.PlayerName))
.ToList().ForEach(p => p.DisposePlayer());
Task.Run(async () => await UpdatePlayersFromService(_onlineCachedPlayers
.Where(p => p.PlayerCharacter != null && p.IsVisible && !p.WasVisible)
.ToDictionary(k => k.PlayerNameHash, k => (int)k.PlayerCharacter!.ClassJob.Id)));

View File

@@ -9,31 +9,34 @@ using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Objects;
using Penumbra.GameData.Structs;
namespace MareSynchronos.Managers
{
public class PlayerManager : IDisposable
{
private readonly ApiController _apiController;
private readonly CachedPlayersManager _cachedPlayersManager;
private readonly OnlinePlayerManager _onlinePlayerManager;
private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private string _lastSentHash = string.Empty;
private CancellationTokenSource? _playerChangedCts;
private DateTime _lastPlayerObjectCheck;
private CharacterEquipment _currentCharacterEquipment;
public PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, CachedPlayersManager cachedPlayersManager, DalamudUtil dalamudUtil)
CharacterDataFactory characterDataFactory, OnlinePlayerManager onlinePlayerManager, DalamudUtil dalamudUtil)
{
Logger.Debug("Creating " + nameof(PlayerManager));
_apiController = apiController;
_ipcManager = ipcManager;
_characterDataFactory = characterDataFactory;
_cachedPlayersManager = cachedPlayersManager;
_onlinePlayerManager = onlinePlayerManager;
_dalamudUtil = dalamudUtil;
_dalamudUtil.AddPlayerToWatch(_dalamudUtil.PlayerName);
_apiController.Connected += ApiController_Connected;
_apiController.Disconnected += ApiController_Disconnected;
@@ -48,12 +51,25 @@ namespace MareSynchronos.Managers
{
Logger.Debug("Disposing " + nameof(PlayerManager));
_dalamudUtil.RemovePlayerFromWatch(_dalamudUtil.PlayerName);
_apiController.Connected -= ApiController_Connected;
_apiController.Disconnected -= ApiController_Disconnected;
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_dalamudUtil.PlayerChanged -= Watcher_PlayerChanged;
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
}
private void DalamudUtilOnFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
if (_dalamudUtil.IsPlayerPresent && !_currentCharacterEquipment!.CompareAndUpdate(_dalamudUtil.PlayerCharacter))
{
OnPlayerChanged();
}
_lastPlayerObjectCheck = DateTime.Now;
}
private void ApiController_Connected(object? sender, EventArgs args)
@@ -64,11 +80,13 @@ namespace MareSynchronos.Managers
Task.WaitAll(apiTask);
_cachedPlayersManager.AddInitialPairs(apiTask.Result);
_onlinePlayerManager.AddInitialPairs(apiTask.Result);
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
_dalamudUtil.PlayerChanged += Watcher_PlayerChanged;
PlayerChanged(_dalamudUtil.PlayerName);
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
_currentCharacterEquipment = new CharacterEquipment(_dalamudUtil.PlayerCharacter);
PlayerChanged();
}
private void ApiController_Disconnected(object? sender, EventArgs args)
@@ -76,7 +94,7 @@ namespace MareSynchronos.Managers
Logger.Debug(nameof(ApiController_Disconnected));
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
_dalamudUtil.PlayerChanged -= Watcher_PlayerChanged;
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
}
private async Task<CharacterData> CreateFullCharacterCache(CancellationToken token)
@@ -102,19 +120,32 @@ namespace MareSynchronos.Managers
private void IpcManager_PenumbraRedrawEvent(object? objectTableIndex, EventArgs e)
{
var player = _dalamudUtil.GetPlayerCharacterFromObjectTableIndex((int)objectTableIndex!);
var player = _dalamudUtil.GetPlayerCharacterFromObjectTableByIndex((int)objectTableIndex!);
if (player != null && player.Name.ToString() != _dalamudUtil.PlayerName) return;
Logger.Debug("Penumbra Redraw Event for " + _dalamudUtil.PlayerName);
PlayerChanged(_dalamudUtil.PlayerName);
PlayerChanged();
}
private void PlayerChanged(string name)
private void PlayerChanged()
{
Logger.Debug("Player changed: " + name);
Logger.Debug("Player changed: " + _dalamudUtil.PlayerName);
_playerChangedCts?.Cancel();
_playerChangedCts = new CancellationTokenSource();
var token = _playerChangedCts.Token;
// fix for redraw from anamnesis
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
{
Logger.Debug("Waiting Until Player is Present");
Thread.Sleep(100);
}
if (token.IsCancellationRequested)
{
Logger.Debug("Cancelled");
return;
}
if (!_ipcManager.Initialized)
{
Logger.Warn("Penumbra not active, doing nothing.");
@@ -159,22 +190,12 @@ namespace MareSynchronos.Managers
}, token);
}
private void Watcher_PlayerChanged(Dalamud.Game.ClientState.Objects.Types.Character actor)
private void OnPlayerChanged()
{
Task.Run(() =>
{
// fix for redraw from anamnesis
while (!_dalamudUtil.IsPlayerPresent)
{
Logger.Debug("Waiting Until Player is Present");
Thread.Sleep(100);
}
if (actor.Name.ToString() == _dalamudUtil.PlayerName)
{
Logger.Debug("Watcher: PlayerChanged");
PlayerChanged(actor.Name.ToString());
}
Logger.Debug("Watcher: PlayerChanged");
PlayerChanged();
});
}