rebuild PlayerManager to CacheCreationService and optimize creation of the local file cache
This commit is contained in:
114
MareSynchronos/Managers/CacheCreationService.cs
Normal file
114
MareSynchronos/Managers/CacheCreationService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user