Files
ClubPenguinClient/MareSynchronos/FileCache/TransientResourceManager.cs
rootdarkarchon 324288652d fix some shit, add triangle count
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
2025-02-16 04:21:02 +00:00

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();
}
}
}