rework transient files to work on a per job/global basis

This commit is contained in:
Stanley Dimant
2024-09-09 21:06:08 +02:00
parent a418aebc4a
commit 114ab2f4e1
4 changed files with 154 additions and 99 deletions

View File

@@ -1,5 +1,6 @@
using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Enum;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.MareConfiguration.Configurations;
using MareSynchronos.PlayerData.Data; using MareSynchronos.PlayerData.Data;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services; using MareSynchronos.Services;
@@ -11,16 +12,18 @@ namespace MareSynchronos.FileCache;
public sealed class TransientResourceManager : DisposableMediatorSubscriberBase public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{ {
private readonly object _cacheAdditionLock = new();
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal); private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
private readonly TransientConfigService _configurationService; private readonly TransientConfigService _configurationService;
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"]; private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = []; private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
private HashSet<IntPtr> _cachedFrameAddresses = []; private Dictionary<IntPtr, ObjectKind> _cachedFrameAddresses = [];
private readonly object _cacheAdditionLock = new(); private ConcurrentDictionary<ObjectKind, HashSet<string>>? _semiTransientResources = null;
private uint _lastClassJobId = uint.MaxValue;
public TransientResourceManager(ILogger<TransientResourceManager> logger, TransientConfigService configurationService, public TransientResourceManager(ILogger<TransientResourceManager> logger, TransientConfigService configurationService,
DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator) DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator)
{ {
_configurationService = configurationService; _configurationService = configurationService;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
@@ -28,13 +31,6 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
Mediator.Subscribe<PenumbraResourceLoadMessage>(this, Manager_PenumbraResourceLoadEvent); Mediator.Subscribe<PenumbraResourceLoadMessage>(this, Manager_PenumbraResourceLoadEvent);
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (_) => Manager_PenumbraModSettingChanged()); Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (_) => Manager_PenumbraModSettingChanged());
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate()); Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
{
if (_playerRelatedPointers.Contains(msg.GameObjectHandler))
{
DalamudUtil_ClassJobChanged();
}
});
Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, (msg) => Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, (msg) =>
{ {
if (!msg.OwnedObject) return; if (!msg.OwnedObject) return;
@@ -47,8 +43,20 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
}); });
} }
private TransientConfig.TransientPlayerConfig PlayerConfig
{
get
{
if (!_configurationService.Current.TransientConfigs.TryGetValue(PlayerPersistentDataKey, out var transientConfig))
{
_configurationService.Current.TransientConfigs[PlayerPersistentDataKey] = transientConfig = new();
}
return transientConfig;
}
}
private string PlayerPersistentDataKey => _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult() + "_" + _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(); private string PlayerPersistentDataKey => _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult() + "_" + _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
private ConcurrentDictionary<ObjectKind, HashSet<string>>? _semiTransientResources = null;
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources
{ {
get get
@@ -56,33 +64,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
if (_semiTransientResources == null) if (_semiTransientResources == null)
{ {
_semiTransientResources = new(); _semiTransientResources = new();
_semiTransientResources.TryAdd(ObjectKind.Player, new HashSet<string>(StringComparer.Ordinal)); PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData);
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var gamePaths)) _semiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? []).ToHashSet(StringComparer.Ordinal);
{
int restored = 0;
foreach (var gamePath in gamePaths)
{
if (string.IsNullOrEmpty(gamePath)) continue;
try
{
Logger.LogDebug("Loaded persistent transient resource {path}", gamePath);
SemiTransientResources[ObjectKind.Player].Add(gamePath);
restored++;
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error during loading persistent transient resource {path}", gamePath);
}
}
Logger.LogDebug("Restored {restored}/{total} semi persistent resources", restored, gamePaths.Count);
}
} }
return _semiTransientResources; return _semiTransientResources;
} }
} }
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new(); private ConcurrentDictionary<ObjectKind, HashSet<string>> TransientResources { get; } = new();
public void CleanUpSemiTransientResources(ObjectKind objectKind, List<FileReplacement>? fileReplacement = null) public void CleanUpSemiTransientResources(ObjectKind objectKind, List<FileReplacement>? fileReplacement = null)
{ {
@@ -103,50 +92,55 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
public HashSet<string> GetSemiTransientResources(ObjectKind objectKind) public HashSet<string> GetSemiTransientResources(ObjectKind objectKind)
{ {
if (SemiTransientResources.TryGetValue(objectKind, out var result)) SemiTransientResources.TryGetValue(objectKind, out var result);
{
return result ?? new HashSet<string>(StringComparer.Ordinal);
}
return new HashSet<string>(StringComparer.Ordinal); return result ?? new HashSet<string>(StringComparer.Ordinal);
} }
public List<string> GetTransientResources(IntPtr gameObject) public void PersistTransientResources(ObjectKind objectKind)
{ {
if (TransientResources.TryGetValue(gameObject, out var result)) if (!SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? semiTransientResources))
{ {
return [.. result]; SemiTransientResources[objectKind] = semiTransientResources = new(StringComparer.Ordinal);
} }
return []; if (!TransientResources.TryGetValue(objectKind, out var resources))
}
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind)
{
if (!SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? value))
{
value = new HashSet<string>(StringComparer.Ordinal);
SemiTransientResources[objectKind] = value;
}
if (!TransientResources.TryGetValue(gameObject, out var resources))
{ {
return; return;
} }
var transientResources = resources.ToList(); var transientResources = resources.ToList();
Logger.LogDebug("Persisting {count} transient resources", transientResources.Count); Logger.LogDebug("Persisting {count} transient resources", transientResources.Count);
List<string> newlyAddedGamePaths = resources.Except(semiTransientResources, StringComparer.Ordinal).ToList();
foreach (var gamePath in transientResources) foreach (var gamePath in transientResources)
{ {
value.Add(gamePath); semiTransientResources.Add(gamePath);
} }
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements)) if (objectKind == ObjectKind.Player && newlyAddedGamePaths.Any())
{ {
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = fileReplacements.Where(f => !string.IsNullOrEmpty(f)).ToHashSet(StringComparer.Ordinal); foreach (var item in newlyAddedGamePaths.Where(f => !string.IsNullOrEmpty(f)))
{
PlayerConfig.AddOrElevate(_dalamudUtil.ClassJobId, item);
}
_configurationService.Save(); _configurationService.Save();
} }
TransientResources[gameObject].Clear();
TransientResources[objectKind].Clear();
}
public void RemoveTransientResource(ObjectKind objectKind, string path)
{
if (SemiTransientResources.TryGetValue(objectKind, out var resources))
{
resources.RemoveWhere(f => string.Equals(path, f, StringComparison.Ordinal));
if (objectKind == ObjectKind.Player)
{
PlayerConfig.RemovePath(path);
_configurationService.Save();
}
}
} }
internal void AddSemiTransientResource(ObjectKind objectKind, string item) internal void AddSemiTransientResource(ObjectKind objectKind, string item)
@@ -160,9 +154,9 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
value.Add(item.ToLowerInvariant()); value.Add(item.ToLowerInvariant());
} }
internal void ClearTransientPaths(IntPtr ptr, List<string> list) internal void ClearTransientPaths(ObjectKind objectKind, List<string> list)
{ {
if (TransientResources.TryGetValue(ptr, out var set)) if (TransientResources.TryGetValue(objectKind, out var set))
{ {
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase)); set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
} }
@@ -174,32 +168,35 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
TransientResources.Clear(); TransientResources.Clear();
SemiTransientResources.Clear(); SemiTransientResources.Clear();
if (SemiTransientResources.TryGetValue(ObjectKind.Player, out HashSet<string>? value))
{
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = value;
_configurationService.Save();
}
}
private void DalamudUtil_ClassJobChanged()
{
if (SemiTransientResources.TryGetValue(ObjectKind.Pet, out HashSet<string>? value))
{
value?.Clear();
}
} }
private void DalamudUtil_FrameworkUpdate() private void DalamudUtil_FrameworkUpdate()
{ {
_cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress()).ToHashSet(); _cachedFrameAddresses = _playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.CurrentAddress(), c => c.ObjectKind);
lock (_cacheAdditionLock) lock (_cacheAdditionLock)
{ {
_cachedHandledPaths.Clear(); _cachedHandledPaths.Clear();
} }
foreach (var item in TransientResources.Where(item => !_dalamudUtil.IsGameObjectPresent(item.Key)).Select(i => i.Key).ToList())
if (_lastClassJobId != _dalamudUtil.ClassJobId)
{ {
Logger.LogDebug("Object not present anymore: {addr}", item.ToString("X")); _lastClassJobId = _dalamudUtil.ClassJobId;
TransientResources.TryRemove(item, out _); if (SemiTransientResources.TryGetValue(ObjectKind.Pet, out HashSet<string>? value))
{
value?.Clear();
}
// reload config for current new classjob
PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData);
SemiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? []).ToHashSet(StringComparer.OrdinalIgnoreCase);
}
foreach (var kind in Enum.GetValues(typeof(ObjectKind)))
{
if (!_cachedFrameAddresses.Any(k => k.Value == (ObjectKind)kind) && TransientResources.Remove((ObjectKind)kind, out _))
{
Logger.LogDebug("Object not present anymore: {kind}", kind.ToString());
}
} }
} }
@@ -252,7 +249,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
} }
// ignore files not belonging to anything player related // ignore files not belonging to anything player related
if (!_cachedFrameAddresses.Contains(gameObject)) if (!_cachedFrameAddresses.TryGetValue(gameObject, out var objectKind))
{ {
lock (_cacheAdditionLock) lock (_cacheAdditionLock)
{ {
@@ -261,14 +258,16 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
return; return;
} }
if (!TransientResources.TryGetValue(gameObject, out HashSet<string>? value)) // ^ all of the code above is just to sanitize the data
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? value))
{ {
value = new(StringComparer.OrdinalIgnoreCase); value = new(StringComparer.OrdinalIgnoreCase);
TransientResources[gameObject] = value; TransientResources[objectKind] = value;
} }
if (value.Contains(replacedGamePath) || if (value.Contains(replacedGamePath)
SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase))) || SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase)))
{ {
Logger.LogTrace("Not adding {replacedPath} : {filePath}", replacedGamePath, filePath); Logger.LogTrace("Not adding {replacedPath} : {filePath}", replacedGamePath, filePath);
} }
@@ -279,14 +278,4 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
Mediator.Publish(new TransientResourceChangedMessage(gameObject)); Mediator.Publish(new TransientResourceChangedMessage(gameObject));
} }
} }
internal void RemoveTransientResource(ObjectKind objectKind, string path)
{
if (SemiTransientResources.TryGetValue(objectKind, out var resources))
{
resources.RemoveWhere(f => string.Equals(path, f, StringComparison.OrdinalIgnoreCase));
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = resources;
_configurationService.Save();
}
}
} }

View File

@@ -2,6 +2,68 @@
public class TransientConfig : IMareConfiguration public class TransientConfig : IMareConfiguration
{ {
public Dictionary<string, HashSet<string>> PlayerPersistentTransientCache { get; set; } = new(StringComparer.Ordinal); public Dictionary<string, TransientPlayerConfig> TransientConfigs { get; set; } = [];
public int Version { get; set; } = 0; public int Version { get; set; } = 0;
public class TransientPlayerConfig
{
public List<string> GlobalPersistentCache { get; set; } = [];
public Dictionary<uint, List<string>> JobSpecificCache { get; set; } = [];
public TransientPlayerConfig()
{
}
private bool ElevateIfNeeded(uint jobId, string gamePath)
{
// check if it's in the job cache of other jobs and elevate if needed
foreach (var kvp in JobSpecificCache)
{
if (kvp.Key == jobId) continue;
// elevate if the gamepath is included somewhere else
if (kvp.Value.Contains(gamePath, StringComparer.Ordinal))
{
JobSpecificCache[kvp.Key].Remove(gamePath);
GlobalPersistentCache.Add(gamePath);
return true;
}
}
return false;
}
public void RemovePath(string gamePath)
{
GlobalPersistentCache.Remove(gamePath);
foreach (var kvp in JobSpecificCache)
{
kvp.Value.Remove(gamePath);
}
}
public void AddOrElevate(uint jobId, string gamePath)
{
// check if it's in the global cache, if yes, do nothing
if (GlobalPersistentCache.Contains(gamePath, StringComparer.Ordinal))
{
return;
}
if (ElevateIfNeeded(jobId, gamePath)) return;
// check if the jobid is already in the cache to start
if (!JobSpecificCache.TryGetValue(jobId, out var jobCache))
{
JobSpecificCache[jobId] = jobCache = new();
}
// check if the path is already in the job specific cache
if (!jobCache.Contains(gamePath, StringComparer.Ordinal))
{
jobCache.Add(gamePath);
}
}
}
} }

View File

@@ -177,10 +177,10 @@ public class PlayerDataFactory
_logger.LogDebug("Handling transient update for {obj}", playerRelatedObject); _logger.LogDebug("Handling transient update for {obj}", playerRelatedObject);
// remove all potentially gathered paths from the transient resource manager that are resolved through static resolving // remove all potentially gathered paths from the transient resource manager that are resolved through static resolving
_transientResourceManager.ClearTransientPaths(charaPointer, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList()); _transientResourceManager.ClearTransientPaths(objectKind, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
// get all remaining paths and resolve them // get all remaining paths and resolve them
var transientPaths = ManageSemiTransientData(objectKind, charaPointer); var transientPaths = ManageSemiTransientData(objectKind);
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false); var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
_logger.LogDebug("== Transient Replacements =="); _logger.LogDebug("== Transient Replacements ==");
@@ -348,9 +348,9 @@ public class PlayerDataFactory
return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly(); return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly();
} }
private HashSet<string> ManageSemiTransientData(ObjectKind objectKind, IntPtr charaPointer) private HashSet<string> ManageSemiTransientData(ObjectKind objectKind)
{ {
_transientResourceManager.PersistTransientResources(charaPointer, objectKind); _transientResourceManager.PersistTransientResources(objectKind);
HashSet<string> pathsToResolve = new(StringComparer.Ordinal); HashSet<string> pathsToResolve = new(StringComparer.Ordinal);
foreach (var path in _transientResourceManager.GetSemiTransientResources(objectKind).Where(path => !string.IsNullOrEmpty(path))) foreach (var path in _transientResourceManager.GetSemiTransientResources(objectKind).Where(path => !string.IsNullOrEmpty(path)))

View File

@@ -82,7 +82,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
public bool IsInCombatOrPerforming { get; private set; } = false; public bool IsInCombatOrPerforming { get; private set; } = false;
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles; public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
public uint ClassJobId => _classJobId!.Value;
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; } public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
public MareMediator Mediator { get; } public MareMediator Mediator { get; }
@@ -555,6 +555,12 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
} }
var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null)
{
_classJobId = localPlayer.ClassJob.Id;
}
if (!IsInCombatOrPerforming) if (!IsInCombatOrPerforming)
Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new FrameworkUpdateMessage());
@@ -563,8 +569,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
if (isNormalFrameworkUpdate) if (isNormalFrameworkUpdate)
return; return;
var localPlayer = _clientState.LocalPlayer;
if (localPlayer != null && !IsLoggedIn) if (localPlayer != null && !IsLoggedIn)
{ {
_logger.LogDebug("Logged in"); _logger.LogDebug("Logged in");