check for invalid animations I hate animations ignore broken bones from god knows what fix more idiotic mod things fully ignore garbage skeletons that fail to process properly fix my own mistakes fix more bullshit check for filename length and continue idk some cleanup fix spoopy skellingtons change loglevel of tris
290 lines
11 KiB
C#
290 lines
11 KiB
C#
using MareSynchronos.API.Data.Enum;
|
|
using MareSynchronos.MareConfiguration;
|
|
using MareSynchronos.PlayerData.Data;
|
|
using MareSynchronos.PlayerData.Handlers;
|
|
using MareSynchronos.Services;
|
|
using MareSynchronos.Services.Mediator;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Collections.Concurrent;
|
|
|
|
namespace MareSynchronos.FileCache;
|
|
|
|
public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|
{
|
|
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
|
|
private readonly TransientConfigService _configurationService;
|
|
private readonly DalamudUtilService _dalamudUtil;
|
|
private readonly string[] _fileTypesToHandle = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
|
|
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
|
|
private HashSet<IntPtr> _cachedFrameAddresses = [];
|
|
private readonly object _cacheAdditionLock = new();
|
|
|
|
public TransientResourceManager(ILogger<TransientResourceManager> logger, TransientConfigService configurationService,
|
|
DalamudUtilService dalamudUtil, MareMediator mediator) : base(logger, mediator)
|
|
{
|
|
_configurationService = configurationService;
|
|
_dalamudUtil = dalamudUtil;
|
|
|
|
Mediator.Subscribe<PenumbraResourceLoadMessage>(this, Manager_PenumbraResourceLoadEvent);
|
|
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (_) => Manager_PenumbraModSettingChanged());
|
|
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
|
|
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
|
|
{
|
|
if (_playerRelatedPointers.Contains(msg.GameObjectHandler))
|
|
{
|
|
DalamudUtil_ClassJobChanged();
|
|
}
|
|
});
|
|
Mediator.Subscribe<AddWatchedGameObjectHandler>(this, (msg) =>
|
|
{
|
|
_playerRelatedPointers.Add(msg.Handler);
|
|
});
|
|
Mediator.Subscribe<RemoveWatchedGameObjectHandler>(this, (msg) =>
|
|
{
|
|
_playerRelatedPointers.Remove(msg.Handler);
|
|
});
|
|
}
|
|
|
|
private string PlayerPersistentDataKey => _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult() + "_" + _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
|
private ConcurrentDictionary<ObjectKind, HashSet<string>>? _semiTransientResources = null;
|
|
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources
|
|
{
|
|
get
|
|
{
|
|
if (_semiTransientResources == null)
|
|
{
|
|
_semiTransientResources = new();
|
|
_semiTransientResources.TryAdd(ObjectKind.Player, new HashSet<string>(StringComparer.Ordinal));
|
|
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var gamePaths))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
|
|
|
public void CleanUpSemiTransientResources(ObjectKind objectKind, List<FileReplacement>? fileReplacement = null)
|
|
{
|
|
if (SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
|
{
|
|
if (fileReplacement == null)
|
|
{
|
|
value.Clear();
|
|
return;
|
|
}
|
|
|
|
foreach (var replacement in fileReplacement.Where(p => !p.HasFileReplacement).SelectMany(p => p.GamePaths).ToList())
|
|
{
|
|
value.RemoveWhere(p => string.Equals(p, replacement, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
}
|
|
|
|
public HashSet<string> GetSemiTransientResources(ObjectKind objectKind)
|
|
{
|
|
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
|
{
|
|
return result ?? new HashSet<string>(StringComparer.Ordinal);
|
|
}
|
|
|
|
return new HashSet<string>(StringComparer.Ordinal);
|
|
}
|
|
|
|
public List<string> GetTransientResources(IntPtr gameObject)
|
|
{
|
|
if (TransientResources.TryGetValue(gameObject, out var result))
|
|
{
|
|
return [.. result];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
var transientResources = resources.ToList();
|
|
Logger.LogDebug("Persisting {count} transient resources", transientResources.Count);
|
|
foreach (var gamePath in transientResources)
|
|
{
|
|
value.Add(gamePath);
|
|
}
|
|
|
|
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
|
|
{
|
|
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = fileReplacements.Where(f => !string.IsNullOrEmpty(f)).ToHashSet(StringComparer.Ordinal);
|
|
_configurationService.Save();
|
|
}
|
|
TransientResources[gameObject].Clear();
|
|
}
|
|
|
|
internal void AddSemiTransientResource(ObjectKind objectKind, string item)
|
|
{
|
|
if (!SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? value))
|
|
{
|
|
value = new HashSet<string>(StringComparer.Ordinal);
|
|
SemiTransientResources[objectKind] = value;
|
|
}
|
|
|
|
value.Add(item.ToLowerInvariant());
|
|
}
|
|
|
|
internal void ClearTransientPaths(IntPtr ptr, List<string> list)
|
|
{
|
|
if (TransientResources.TryGetValue(ptr, out var set))
|
|
{
|
|
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
|
|
TransientResources.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()
|
|
{
|
|
_cachedFrameAddresses = _playerRelatedPointers.Select(c => c.CurrentAddress()).ToHashSet();
|
|
lock (_cacheAdditionLock)
|
|
{
|
|
_cachedHandledPaths.Clear();
|
|
}
|
|
foreach (var item in TransientResources.Where(item => !_dalamudUtil.IsGameObjectPresent(item.Key)).Select(i => i.Key).ToList())
|
|
{
|
|
Logger.LogDebug("Object not present anymore: {addr}", item.ToString("X"));
|
|
TransientResources.TryRemove(item, out _);
|
|
}
|
|
}
|
|
|
|
private void Manager_PenumbraModSettingChanged()
|
|
{
|
|
_ = Task.Run(() =>
|
|
{
|
|
Logger.LogDebug("Penumbra Mod Settings changed, verifying SemiTransientResources");
|
|
foreach (var item in _playerRelatedPointers)
|
|
{
|
|
Mediator.Publish(new TransientResourceChangedMessage(item.Address));
|
|
}
|
|
});
|
|
}
|
|
|
|
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
|
|
{
|
|
var gamePath = msg.GamePath.ToLowerInvariant();
|
|
var gameObject = msg.GameObject;
|
|
var filePath = msg.FilePath;
|
|
|
|
// ignore files already processed this frame
|
|
if (_cachedHandledPaths.Contains(gamePath)) return;
|
|
|
|
lock (_cacheAdditionLock)
|
|
{
|
|
_cachedHandledPaths.Add(gamePath);
|
|
}
|
|
|
|
// replace individual mtrl stuff
|
|
if (filePath.StartsWith("|", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
filePath = filePath.Split("|")[2];
|
|
}
|
|
// replace filepath
|
|
filePath = filePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase);
|
|
|
|
// ignore files that are the same
|
|
var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase);
|
|
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
|
|
|
// ignore files to not handle
|
|
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
lock (_cacheAdditionLock)
|
|
{
|
|
_cachedHandledPaths.Add(gamePath);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// ignore files not belonging to anything player related
|
|
if (!_cachedFrameAddresses.Contains(gameObject))
|
|
{
|
|
lock (_cacheAdditionLock)
|
|
{
|
|
_cachedHandledPaths.Add(gamePath);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!TransientResources.TryGetValue(gameObject, out HashSet<string>? value))
|
|
{
|
|
value = new(StringComparer.OrdinalIgnoreCase);
|
|
TransientResources[gameObject] = value;
|
|
}
|
|
|
|
if (value.Contains(replacedGamePath) ||
|
|
SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
Logger.LogTrace("Not adding {replacedPath} : {filePath}", replacedGamePath, filePath);
|
|
}
|
|
else
|
|
{
|
|
value.Add(replacedGamePath);
|
|
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, gameObject.ToString("X"), filePath);
|
|
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();
|
|
}
|
|
}
|
|
} |