rebuild PlayerManager to CacheCreationService and optimize creation of the local file cache

This commit is contained in:
rootdarkarchon
2023-02-02 15:25:58 +01:00
parent 86549b2d3f
commit ede62fabae
23 changed files with 461 additions and 737 deletions

View File

@@ -0,0 +1,114 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Factories;
using MareSynchronos.Mediator;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class CacheCreationService : MediatorSubscriberBase, IDisposable
{
private readonly CharacterDataFactory _characterDataFactory;
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private Task? _cacheCreationTask;
private Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = new();
private CharacterData _lastCreatedData = new();
private CancellationTokenSource cts = new();
private List<GameObjectHandler> _playerRelatedObjects;
public unsafe CacheCreationService(MareMediator mediator, CharacterDataFactory characterDataFactory, IpcManager ipcManager,
ApiController apiController, DalamudUtil dalamudUtil) : base(mediator)
{
_characterDataFactory = characterDataFactory;
_ipcManager = ipcManager;
_apiController = apiController;
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
{
var actualMsg = (CreateCacheForObjectMessage)msg;
_cachesToCreate[actualMsg.ObjectToCreateFor.ObjectKind] = actualMsg.ObjectToCreateFor;
});
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
_playerRelatedObjects = new List<GameObjectHandler>()
{
new(Mediator, ObjectKind.Player, () => dalamudUtil.PlayerPointer),
new(Mediator, ObjectKind.MinionOrMount, () => (IntPtr)((Character*)dalamudUtil.PlayerPointer)->CompanionObject),
new(Mediator, ObjectKind.Pet, () => dalamudUtil.GetPet()),
new(Mediator, ObjectKind.Companion, () => dalamudUtil.GetCompanion()),
};
}
private void PalettePlusChanged(PalettePlusMessage msg)
{
if (!string.Equals(msg.Data, _lastCreatedData.PalettePlusPalette, StringComparison.Ordinal))
{
_lastCreatedData.PalettePlusPalette = msg.Data ?? string.Empty;
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
}
}
private void HeelsOffsetChanged(HeelsOffsetMessage msg)
{
if (msg.Offset != _lastCreatedData.HeelsOffset)
{
_lastCreatedData.HeelsOffset = msg.Offset;
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
}
}
private void CustomizePlusChanged(CustomizePlusMessage msg)
{
if (!string.Equals(msg.Data, _lastCreatedData.CustomizePlusScale, StringComparison.Ordinal))
{
_lastCreatedData.CustomizePlusScale = msg.Data ?? string.Empty;
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
}
}
private void ProcessCacheCreation()
{
if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true))
{
var toCreate = _cachesToCreate.ToList();
_cachesToCreate.Clear();
_cacheCreationTask = Task.Run(() =>
{
try
{
foreach (var obj in toCreate)
{
var data = _characterDataFactory.BuildCharacterData(_lastCreatedData, obj.Value, cts.Token);
}
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
}
catch (Exception ex)
{
Logger.Error("Error during Cache Creation Processing", ex);
}
finally
{
Logger.Debug("Cache Creation complete");
}
}, cts.Token);
}
else if (_cachesToCreate.Any())
{
Logger.Debug("Cache Creation stored until previous creation finished");
}
}
public override void Dispose()
{
base.Dispose();
_playerRelatedObjects.ForEach(p => p.Dispose());
cts.Dispose();
}
}

View File

@@ -19,7 +19,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
private readonly IpcManager _ipcManager;
private readonly FileCacheManager _fileDbManager;
private API.Data.CharacterData _cachedData = new();
private PlayerRelatedObject? _currentCharacterEquipment;
private GameObjectHandler? _currentCharacterEquipment;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _isVisible;
@@ -65,6 +65,18 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
Logger.Debug("Checking for files to download for player " + PlayerName);
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
if (!_ipcManager.CheckPenumbraApi())
{
Mediator.Publish(new NotificationMessage("Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare.", NotificationType.Error));
return;
}
if (!_ipcManager.CheckGlamourerApi())
{
Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare.", NotificationType.Error));
return;
}
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal) && !forced) return;
bool updateModdedPaths = false;
@@ -116,6 +128,14 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
if (objectKind == ObjectKind.Player)
{
bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, characterData.ManipulationData, StringComparison.Ordinal);
if (manipDataDifferent)
{
Logger.Debug("Updating " + objectKind);
charaDataToUpdate.Add(objectKind);
continue;
}
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
if (heelsOffsetDifferent)
{
@@ -189,8 +209,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
return false;
}
_currentCharacterEquipment?.CheckAndUpdateObject();
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
if (_currentCharacterEquipment?.CheckAndUpdateObject() ?? false)
{
OnPlayerChanged();
}
@@ -247,8 +266,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg)));
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
_currentCharacterEquipment = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, false);
}
public override string ToString()
@@ -437,7 +455,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
{
PlayerCharacter = msg.Address;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
cts.CancelAfter(TimeSpan.FromSeconds(10));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
@@ -460,7 +478,6 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
private void OnPlayerChanged()
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
Logger.Debug($"Saving new Glamourer data");

View File

@@ -35,8 +35,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
private readonly FuncSubscriber<string, string> _penumbraResolvePlayer;
private readonly FuncSubscriber<string, string[]> _reverseResolvePlayer;
private readonly FuncSubscriber<string, string, Dictionary<string, string>, string, int, PenumbraApiEc> _penumbraAddTemporaryMod;
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
private readonly FuncSubscriber<bool> _penumbraEnabled;
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
private readonly EventSubscriber<ModSettingChange, string, string, bool> _penumbraModSettingChanged;
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
private readonly ICallGateSubscriber<float> _heelsGetOffset;
@@ -49,7 +50,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
private readonly ICallGateSubscriber<string, Character?, object> _customizePlusSetBodyScaleToCharacter;
private readonly ICallGateSubscriber<Character?, object> _customizePlusRevert;
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
private readonly ICallGateSubscriber<string> _palettePlusApiVersion;
private readonly ICallGateSubscriber<Character, string> _palettePlusBuildCharaPalette;
private readonly ICallGateSubscriber<Character, string, object> _palettePlusSetCharaPalette;
@@ -81,9 +82,10 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
_penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi);
_penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi);
_penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi);
_penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi);
_penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi);
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, (ptr, arg1, arg2) => ResourceLoaded((IntPtr)ptr, arg1, arg2));
_penumbraModSettingChanged = Penumbra.Api.Ipc.ModSettingChanged.Subscriber(pi, (modsetting, a, b, c) => PenumbraModSettingChangedHandler());
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
@@ -107,7 +109,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
_customizePlusOnScaleUpdate = pi.GetIpcSubscriber<string?, object>("CustomizePlus.OnScaleUpdate");
_customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange);
_palettePlusApiVersion = pi.GetIpcSubscriber<string>("PalettePlus.ApiVersion");
_palettePlusBuildCharaPalette = pi.GetIpcSubscriber<Character, string>("PalettePlus.BuildCharaPalette");
_palettePlusSetCharaPalette = pi.GetIpcSubscriber<Character, string, object>("PalettePlus.SetCharaPalette");
@@ -125,6 +127,12 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => HandleActionQueue());
Mediator.Subscribe<GposeFrameworkUpdateMessage>(this, (_) => HandleGposeActionQueue());
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (_) => ClearActionQueue());
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => CheckPenumbraModPath());
}
private void CheckPenumbraModPath()
{
PenumbraModDirectory = GetPenumbraModDirectory();
}
private void HandleGposeActionQueue()
@@ -142,11 +150,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
_inGposeQueueMode = on;
}
private void PenumbraModSettingChangedHandler()
{
Mediator.Publish(new PenumbraModSettingChangedMessage());
}
private void ClearActionQueue()
{
ActionQueue.Clear();
@@ -191,7 +194,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
{
try
{
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 17 };
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke();
}
catch
{
@@ -259,7 +262,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
_penumbraDispose.Dispose();
_penumbraInit.Dispose();
_penumbraObjectIsRedrawn.Dispose();
_penumbraModSettingChanged.Dispose();
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
}
@@ -411,7 +413,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
return _penumbraGetMetaManipulations.Invoke();
}
public string? PenumbraModDirectory()
public string? PenumbraModDirectory;
public string? GetPenumbraModDirectory()
{
if (!CheckPenumbraApi()) return null;
return _penumbraResolveModDir!.Invoke().ToLowerInvariant();
@@ -495,6 +499,11 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
});
}
public (string[] forward, string[][] reverse) PenumbraResolvePaths(string[] forward, string[] reverse)
{
return _penumbraResolvePaths.Invoke(forward, reverse);
}
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
{
Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex));
@@ -533,7 +542,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
if (gameObj is Character c)
{
string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette));
if (string.IsNullOrEmpty(decodedPalette))
{
Logger.Verbose("PalettePlus removing for " + c.Address.ToString("X"));
@@ -555,7 +564,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
if (string.IsNullOrEmpty(palette)) return string.Empty;
return Convert.ToBase64String(Encoding.UTF8.GetBytes(palette));
}
public void PalettePlusRemovePalette(IntPtr character)
{
if (!CheckPalettePlusApi()) return;

View File

@@ -97,7 +97,7 @@ public class NotificationService : MediatorSubscriberBase
private void PrintErrorChat(string? message)
{
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Error: ", 534).AddItalicsOn().AddUiForeground(message ?? string.Empty, 534).AddUiForegroundOff().AddItalicsOff();
_chatGui.Print(se.BuiltString);
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Error: " + message);
_chatGui.PrintError(se.BuiltString);
}
}

View File

@@ -11,18 +11,17 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
{
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly PlayerManager _playerManager;
private readonly FileCacheManager _fileDbManager;
private readonly PairManager _pairManager;
private CharacterData? _lastSentData;
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, PlayerManager playerManager,
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil,
FileCacheManager fileDbManager, PairManager pairManager, MareMediator mediator) : base(mediator)
{
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
_apiController = apiController;
_dalamudUtil = dalamudUtil;
_playerManager = playerManager;
_fileDbManager = fileDbManager;
_pairManager = pairManager;
@@ -30,6 +29,20 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
{
var newData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI();
if (_lastSentData == null || _lastSentData != null && !string.Equals(newData.DataHash.Value, _lastSentData.DataHash.Value, StringComparison.Ordinal))
{
Logger.Debug("Pushing data for visible players");
_lastSentData = newData;
PushCharacterData(_pairManager.VisibleUsers);
}
else
{
Logger.Debug("Not sending data for " + newData.DataHash.Value);
}
});
}
private void PlayerManagerOnPlayerHasChanged(PlayerChangedMessage msg)
@@ -78,11 +91,11 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
private void PushCharacterData(List<UserData> visiblePlayers)
{
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
if (visiblePlayers.Any() && _lastSentData != null)
{
Task.Run(async () =>
{
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, visiblePlayers).ConfigureAwait(false);
await _apiController.PushCharacterData(_lastSentData, visiblePlayers).ConfigureAwait(false);
});
}
}

View File

@@ -1,5 +1,4 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Utility;
using MareSynchronos.API.Data;

View File

@@ -1,279 +0,0 @@
using MareSynchronos.Factories;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using MareSynchronos.Models;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Mediator;
#if DEBUG
#endif
namespace MareSynchronos.Managers;
public class PlayerManager : MediatorSubscriberBase, IDisposable
{
private readonly ApiController _apiController;
private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
public API.Data.CharacterData? LastCreatedCharacterData { get; private set; }
public Models.CharacterData PermanentDataCache { get; private set; } = new();
private readonly Dictionary<ObjectKind, Func<bool>> _objectKindsToUpdate = new();
private CancellationTokenSource? _playerChangedCts = new();
private CancellationTokenSource _transientUpdateCts = new();
private readonly List<PlayerRelatedObject> _playerRelatedObjects = new();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil,
MareMediator mediator) : base(mediator)
{
Logger.Verbose("Creating " + nameof(PlayerManager));
_apiController = apiController;
_ipcManager = ipcManager;
_characterDataFactory = characterDataFactory;
_dalamudUtil = dalamudUtil;
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
Mediator.Subscribe<ConnectedMessage>(this, (_) => ApiControllerOnConnected());
Mediator.Subscribe<DisconnectedMessage>(this, (_) => ApiController_Disconnected());
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => DalamudUtilOnDelayedFrameworkUpdate());
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtilOnFrameworkUpdate());
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) => HandleTransientResourceLoad((TransientResourceChangedMessage)msg));
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
if (_apiController.IsConnected)
{
ApiControllerOnConnected();
}
_playerRelatedObjects = new List<PlayerRelatedObject>()
{
new(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
new(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
new(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
new(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
};
}
private void DalamudUtilOnFrameworkUpdate()
{
Mediator.Publish(new PlayerRelatedObjectPointerUpdateMessage(_playerRelatedObjects.Select(f => f.CurrentAddress).ToArray()));
}
public void HandleTransientResourceLoad(TransientResourceChangedMessage msg)
{
foreach (var obj in _playerRelatedObjects)
{
if (obj.Address == msg.Address && !obj.HasUnprocessedUpdate)
{
_transientUpdateCts.Cancel();
_transientUpdateCts = new CancellationTokenSource();
var token = _transientUpdateCts.Token;
Task.Run(async () =>
{
Logger.Debug("Delaying transient resource load update");
await Task.Delay(750, token).ConfigureAwait(false);
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
Logger.Debug("Firing transient resource load update");
obj.HasTransientsUpdate = true;
}, token);
return;
}
}
}
private void HeelsOffsetChanged(HeelsOffsetMessage change)
{
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change.Offset && !player.IsProcessing)
{
Logger.Debug("Heels offset changed to " + change.Offset);
player.HasTransientsUpdate = true;
}
}
private void CustomizePlusChanged(CustomizePlusMessage msg)
{
var change = msg.Data ?? string.Empty;
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.CustomizePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
{
Logger.Debug("CustomizePlus data changed to " + change);
player.HasTransientsUpdate = true;
}
}
private void PalettePlusChanged(PalettePlusMessage msg)
{
var change = msg.Data ?? string.Empty;
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.PalettePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
{
Logger.Debug("PalettePlus data changed to " + change);
player.HasTransientsUpdate = true;
}
}
public override void Dispose()
{
base.Dispose();
_playerChangedCts?.Cancel();
}
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
_playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
{
OnPlayerOrAttachedObjectsChanged();
}
}
private void ApiControllerOnConnected()
{
Logger.Debug("ApiController Connected");
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManager_PenumbraRedrawEvent((PenumbraRedrawMessage)msg));
}
private void ApiController_Disconnected()
{
Logger.Debug(nameof(ApiController_Disconnected));
Mediator.Unsubscribe<PenumbraRedrawMessage>(this);
}
private async Task<API.Data.CharacterData?> CreateFullCharacterCacheDto(CancellationToken token)
{
foreach (var unprocessedObject in _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
{
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
if (!token.IsCancellationRequested)
{
unprocessedObject.HasUnprocessedUpdate = false;
unprocessedObject.IsProcessing = false;
unprocessedObject.HasTransientsUpdate = false;
}
token.ThrowIfCancellationRequested();
}
int timeOut = 10000;
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested && timeOut >= 0)
{
Logger.Verbose("Waiting until cache is ready (Timeout: " + TimeSpan.FromMilliseconds(timeOut) + ")");
await Task.Delay(50, token).ConfigureAwait(false);
timeOut -= 50;
}
if (token.IsCancellationRequested || timeOut <= 0) return null;
Logger.Verbose("Cache creation complete");
var cache = PermanentDataCache.ToAPI();
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
return cache;
}
private void IpcManager_PenumbraRedrawEvent(PenumbraRedrawMessage msg)
{
Logger.Verbose("RedrawEvent for addr " + msg.Address);
foreach (var item in _playerRelatedObjects)
{
if (msg.Address == item.Address)
{
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
item.HasUnprocessedUpdate = true;
}
}
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
{
OnPlayerOrAttachedObjectsChanged();
}
}
private void OnPlayerOrAttachedObjectsChanged()
{
var unprocessedObjects = _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
foreach (var unprocessedObject in unprocessedObjects)
{
unprocessedObject.IsProcessing = true;
}
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate);
unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false);
_playerChangedCts?.Cancel();
_playerChangedCts = new CancellationTokenSource();
var token = _playerChangedCts.Token;
// fix for redraw from anamnesis
while ((!_dalamudUtil.IsPlayerPresent || string.Equals(_dalamudUtil.PlayerName, "--", StringComparison.Ordinal)) && !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.");
return;
}
Task.Run(async () =>
{
API.Data.CharacterData? cacheData = null;
try
{
Mediator.Publish(new HaltScanMessage("Character creation"));
foreach (var item in unprocessedObjects)
{
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, item.ObjectKind == ObjectKind.MinionOrMount ? 1000 : 10000, token);
}
cacheData = (await CreateFullCharacterCacheDto(token).ConfigureAwait(false));
}
catch { }
finally
{
Mediator.Publish(new ResumeScanMessage("Character creation"));
}
if (cacheData == null || token.IsCancellationRequested) return;
#if DEBUG
//var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented);
//Logger.Verbose(json);
#endif
if (string.Equals(LastCreatedCharacterData?.DataHash.Value ?? string.Empty, cacheData.DataHash.Value, StringComparison.Ordinal))
{
Logger.Debug("Not sending data, already sent");
return;
}
LastCreatedCharacterData = cacheData;
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
{
Logger.Verbose("Invoking PlayerHasChanged");
Mediator.Publish(new PlayerChangedMessage(cacheData));
}
}, token);
}
}

View File

@@ -1,8 +1,6 @@
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Factories;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Mediator;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using System.Collections.Concurrent;
@@ -16,13 +14,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
[Obsolete]
private string PersistentDataCache => Path.Combine(_configurationService.ConfigurationDirectory, "PersistentTransientData.lst");
private string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory, MareMediator mediator) : base(mediator)
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources { get; } = new();
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, MareMediator mediator) : base(mediator)
{
_configurationService = configurationService;
_dalamudUtil = dalamudUtil;
@@ -32,16 +28,8 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
Mediator.Subscribe<ClassJobChangedMessage>(this, (_) => DalamudUtil_ClassJobChanged());
Mediator.Subscribe<PlayerRelatedObjectPointerUpdateMessage>(this, (msg) => PlayerRelatedPointers = ((PlayerRelatedObjectPointerUpdateMessage)msg).RelatedObjects);
// migrate obsolete data to new format
if (File.Exists(PersistentDataCache))
{
var persistentEntities = File.ReadAllLines(PersistentDataCache).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = persistentEntities;
_configurationService.Save();
File.Delete(PersistentDataCache);
}
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<string>(StringComparer.Ordinal));
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
{
int restored = 0;
@@ -49,14 +37,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
{
try
{
var fileReplacement = fileReplacementFactory.Create();
fileReplacement.ResolvePath(file);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Loaded persistent transient resource " + file);
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
restored++;
}
Logger.Debug("Loaded persistent transient resource " + file);
SemiTransientResources[ObjectKind.Player].Add(file);
restored++;
}
catch (Exception ex)
{
@@ -70,20 +53,12 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
private void Manager_PenumbraModSettingChanged()
{
bool successfulValidation = true;
Task.Run(() =>
{
Logger.Debug("Penumbra Mod Settings changed, verifying SemiTransientResources");
foreach (var item in SemiTransientResources)
{
item.Value.RemoveWhere(p =>
{
var verified = p.Verify();
successfulValidation &= verified;
return !verified;
});
if (!successfulValidation)
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
}
});
}
@@ -126,19 +101,19 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
return new List<string>();
}
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
public HashSet<string> GetSemiTransientResources(ObjectKind objectKind)
{
if (SemiTransientResources.TryGetValue(objectKind, out var result))
{
return result.ToList();
return result;
}
return new List<FileReplacement>();
return new HashSet<string>();
}
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
{
var gamePath = msg.GamePath;
var gamePath = msg.GamePath.ToLowerInvariant();
var gameObject = msg.GameObject;
var filePath = msg.FilePath;
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
@@ -166,14 +141,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
if (TransientResources[gameObject].Contains(replacedGamePath) ||
SemiTransientResources.Any(r => r.Value.Any(f =>
string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))
))
SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase))))
{
Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath);
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
}
else
{
@@ -183,19 +153,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
}
}
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
{
if (TransientResources.ContainsKey(gameObject))
{
TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => string.Equals(g, f, StringComparison.OrdinalIgnoreCase)));
}
}
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind)
{
if (!SemiTransientResources.ContainsKey(objectKind))
{
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
}
if (!TransientResources.TryGetValue(gameObject, out var resources))
@@ -203,47 +165,16 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
return;
}
SemiTransientResources[objectKind].RemoveWhere(p => !p.Verify());
var transientResources = resources.ToList();
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
foreach (var gamePath in transientResources)
{
var existingResource = SemiTransientResources[objectKind].Any(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
if (existingResource)
{
Logger.Debug("Semi Transient resource replaced: " + gamePath);
SemiTransientResources[objectKind].RemoveWhere(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
}
try
{
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: true);
if (!fileReplacement.HasFileReplacement)
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: false);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
if (SemiTransientResources[objectKind].Add(fileReplacement))
{
Logger.Debug("Added " + fileReplacement);
}
else
{
Logger.Debug("Not added " + fileReplacement);
}
}
}
catch (Exception ex)
{
Logger.Warn("Issue during transient file persistence", ex);
}
SemiTransientResources[objectKind].Add(gamePath);
}
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
{
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = fileReplacements;
_configurationService.Save();
}
TransientResources[gameObject].Clear();
@@ -256,22 +187,26 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
SemiTransientResources.Clear();
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
{
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = SemiTransientResources[ObjectKind.Player];
_configurationService.Save();
}
}
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
internal void AddSemiTransientResource(ObjectKind objectKind, string item)
{
if (!SemiTransientResources.ContainsKey(objectKind))
{
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
}
if (!SemiTransientResources[objectKind].Any(f => string.Equals(f.ResolvedPath, item.ResolvedPath, StringComparison.OrdinalIgnoreCase)))
SemiTransientResources[objectKind].Add(item.ToLowerInvariant());
}
internal void ClearTransientPaths(IntPtr ptr, List<string> list)
{
if (TransientResources.TryGetValue(ptr, out var set))
{
SemiTransientResources[objectKind].Add(item);
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
}
}
}