Merge conf

This commit is contained in:
defnotken
2025-12-27 21:17:26 -06:00
32 changed files with 3858 additions and 723 deletions

View File

@@ -1,15 +1,23 @@
#nullable disable
using System.Text.Json.Serialization;
namespace LightlessSync.FileCache;
public class FileCacheEntity
{
public FileCacheEntity(string hash, string path, string lastModifiedDateTicks, long? size = null, long? compressedSize = null)
[JsonConstructor]
public FileCacheEntity(
string hash,
string prefixedFilePath,
string lastModifiedDateTicks,
long? size = null,
long? compressedSize = null)
{
Size = size;
CompressedSize = compressedSize;
Hash = hash;
PrefixedFilePath = path;
PrefixedFilePath = prefixedFilePath;
LastModifiedDateTicks = lastModifiedDateTicks;
}
@@ -23,7 +31,5 @@ public class FileCacheEntity
public long? Size { get; set; }
public void SetResolvedFilePath(string filePath)
{
ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", StringComparison.Ordinal);
}
=> ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", StringComparison.Ordinal);
}

View File

@@ -7,6 +7,8 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace LightlessSync.FileCache;
@@ -31,6 +33,14 @@ public sealed class FileCacheManager : IHostedService
private bool _csvHeaderEnsured;
public string CacheFolder => _configService.Current.CacheFolder;
private const string _compressedCacheExtension = ".llz4";
private readonly ConcurrentDictionary<string, SemaphoreSlim> _compressLocks = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, SizeInfo> _sizeCache =
new(StringComparer.OrdinalIgnoreCase);
[StructLayout(LayoutKind.Auto)]
public readonly record struct SizeInfo(long Original, long Compressed);
public FileCacheManager(ILogger<FileCacheManager> logger, IpcManager ipcManager, LightlessConfigService configService, LightlessMediator lightlessMediator)
{
_logger = logger;
@@ -45,6 +55,18 @@ public sealed class FileCacheManager : IHostedService
private static string NormalizeSeparators(string path) => path.Replace("/", "\\", StringComparison.Ordinal)
.Replace("\\\\", "\\", StringComparison.Ordinal);
private SemaphoreSlim GetCompressLock(string hash)
=> _compressLocks.GetOrAdd(hash, _ => new SemaphoreSlim(1, 1));
public void SetSizeInfo(string hash, long original, long compressed)
=> _sizeCache[hash] = new SizeInfo(original, compressed);
public bool TryGetSizeInfo(string hash, out SizeInfo info)
=> _sizeCache.TryGetValue(hash, out info);
private string GetCompressedCachePath(string hash)
=> Path.Combine(CacheFolder, hash + _compressedCacheExtension);
private static string NormalizePrefixedPathKey(string prefixedPath)
{
if (string.IsNullOrEmpty(prefixedPath))
@@ -111,6 +133,114 @@ public sealed class FileCacheManager : IHostedService
return int.TryParse(versionSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out version);
}
public void UpdateSizeInfo(string hash, long? original = null, long? compressed = null)
{
_sizeCache.AddOrUpdate(
hash,
_ => new SizeInfo(original ?? 0, compressed ?? 0),
(_, old) => new SizeInfo(original ?? old.Original, compressed ?? old.Compressed));
}
private void UpdateEntitiesSizes(string hash, long original, long compressed)
{
if (_fileCaches.TryGetValue(hash, out var dict))
{
foreach (var e in dict.Values)
{
e.Size = original;
e.CompressedSize = compressed;
}
}
}
public static void ApplySizesToEntries(IEnumerable<FileCacheEntity?> entries, long original, long compressed)
{
foreach (var e in entries)
{
if (e == null) continue;
e.Size = original;
e.CompressedSize = compressed > 0 ? compressed : null;
}
}
public async Task<long> GetCompressedSizeAsync(string hash, CancellationToken token)
{
if (_sizeCache.TryGetValue(hash, out var info) && info.Compressed > 0)
return info.Compressed;
if (_fileCaches.TryGetValue(hash, out var dict))
{
var any = dict.Values.FirstOrDefault();
if (any != null && any.CompressedSize > 0)
{
UpdateSizeInfo(hash, original: any.Size > 0 ? any.Size : null, compressed: any.CompressedSize);
return (long)any.CompressedSize;
}
}
if (!string.IsNullOrWhiteSpace(CacheFolder))
{
var path = GetCompressedCachePath(hash);
if (File.Exists(path))
{
var len = new FileInfo(path).Length;
UpdateSizeInfo(hash, compressed: len);
return len;
}
var bytes = await EnsureCompressedCacheBytesAsync(hash, token).ConfigureAwait(false);
return bytes.LongLength;
}
var fallback = await GetCompressedFileData(hash, token).ConfigureAwait(false);
return fallback.Item2.LongLength;
}
private async Task<byte[]> EnsureCompressedCacheBytesAsync(string hash, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(CacheFolder))
throw new InvalidOperationException("CacheFolder is not set; cannot persist compressed cache.");
Directory.CreateDirectory(CacheFolder);
var compressedPath = GetCompressedCachePath(hash);
if (File.Exists(compressedPath))
return await File.ReadAllBytesAsync(compressedPath, token).ConfigureAwait(false);
var sem = GetCompressLock(hash);
await sem.WaitAsync(token).ConfigureAwait(false);
try
{
if (File.Exists(compressedPath))
return await File.ReadAllBytesAsync(compressedPath, token).ConfigureAwait(false);
var entity = GetFileCacheByHash(hash);
if (entity == null || string.IsNullOrWhiteSpace(entity.ResolvedFilepath))
throw new InvalidOperationException($"No local file cache found for hash {hash}.");
var sourcePath = entity.ResolvedFilepath;
var originalSize = new FileInfo(sourcePath).Length;
var raw = await File.ReadAllBytesAsync(sourcePath, token).ConfigureAwait(false);
var compressed = LZ4Wrapper.WrapHC(raw, 0, raw.Length);
var tmpPath = compressedPath + ".tmp";
await File.WriteAllBytesAsync(tmpPath, compressed, token).ConfigureAwait(false);
File.Move(tmpPath, compressedPath, overwrite: true);
var compressedSize = compressed.LongLength;
SetSizeInfo(hash, originalSize, compressedSize);
UpdateEntitiesSizes(hash, originalSize, compressedSize);
return compressed;
}
finally
{
sem.Release();
}
}
private string NormalizeToPrefixedPath(string path)
{
if (string.IsNullOrEmpty(path)) return string.Empty;
@@ -318,9 +448,18 @@ public sealed class FileCacheManager : IHostedService
public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{
if (!string.IsNullOrWhiteSpace(CacheFolder))
{
var bytes = await EnsureCompressedCacheBytesAsync(fileHash, uploadToken).ConfigureAwait(false);
UpdateSizeInfo(fileHash, compressed: bytes.LongLength);
return (fileHash, bytes);
}
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath;
return (fileHash, LZ4Wrapper.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false), 0,
(int)new FileInfo(fileCache).Length));
var raw = await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false);
var compressed = LZ4Wrapper.WrapHC(raw, 0, raw.Length);
UpdateSizeInfo(fileHash, original: raw.LongLength, compressed: compressed.LongLength);
return (fileHash, compressed);
}
public FileCacheEntity? GetFileCacheByHash(string hash)
@@ -891,6 +1030,14 @@ public sealed class FileCacheManager : IHostedService
compressed = resultCompressed;
}
}
if (size > 0 || compressed > 0)
{
UpdateSizeInfo(hash,
original: size > 0 ? size : null,
compressed: compressed > 0 ? compressed : null);
}
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
}
catch (Exception ex)

View File

@@ -10,9 +10,6 @@ using LightlessSync.Services.Mediator;
using LightlessSync.Utils;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
namespace LightlessSync.FileCache;
@@ -28,7 +25,10 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
private readonly object _ownedHandlerLock = new();
private readonly string[] _handledFileTypes = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk", "kdb"];
private readonly string[] _handledRecordingFileTypes = ["tex", "mdl", "mtrl"];
private readonly string[] _handledFileTypesWithRecording;
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
private readonly object _playerRelatedLock = new();
private readonly ConcurrentDictionary<nint, GameObjectHandler> _playerRelatedByAddress = new();
private readonly Dictionary<nint, GameObjectHandler> _ownedHandlers = new();
private ConcurrentDictionary<nint, ObjectKind> _cachedFrameAddresses = new();
private ConcurrentDictionary<ObjectKind, HashSet<string>>? _semiTransientResources = null;
@@ -42,6 +42,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
_dalamudUtil = dalamudUtil;
_actorObjectService = actorObjectService;
_gameObjectHandlerFactory = gameObjectHandlerFactory;
_handledFileTypesWithRecording = _handledRecordingFileTypes.Concat(_handledFileTypes).ToArray();
Mediator.Subscribe<PenumbraResourceLoadMessage>(this, Manager_PenumbraResourceLoadEvent);
Mediator.Subscribe<ActorTrackedMessage>(this, msg => HandleActorTracked(msg.Descriptor));
@@ -51,12 +52,18 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
Mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, (msg) =>
{
if (!msg.OwnedObject) return;
_playerRelatedPointers.Add(msg.GameObjectHandler);
lock (_playerRelatedLock)
{
_playerRelatedPointers.Add(msg.GameObjectHandler);
}
});
Mediator.Subscribe<GameObjectHandlerDestroyedMessage>(this, (msg) =>
{
if (!msg.OwnedObject) return;
_playerRelatedPointers.Remove(msg.GameObjectHandler);
lock (_playerRelatedLock)
{
_playerRelatedPointers.Remove(msg.GameObjectHandler);
}
});
foreach (var descriptor in _actorObjectService.ObjectDescriptors)
@@ -87,9 +94,12 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{
_semiTransientResources = new();
PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData);
_semiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? []).ToHashSet(StringComparer.Ordinal);
_semiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? [])
.ToHashSet(StringComparer.OrdinalIgnoreCase);
PlayerConfig.JobSpecificPetCache.TryGetValue(_dalamudUtil.ClassJobId, out var petSpecificData);
_semiTransientResources[ObjectKind.Pet] = [.. petSpecificData ?? []];
_semiTransientResources[ObjectKind.Pet] = new HashSet<string>(
petSpecificData ?? [],
StringComparer.OrdinalIgnoreCase);
}
return _semiTransientResources;
@@ -127,14 +137,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{
SemiTransientResources.TryGetValue(objectKind, out var result);
return result ?? new HashSet<string>(StringComparer.Ordinal);
return result ?? new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
public void PersistTransientResources(ObjectKind objectKind)
{
if (!SemiTransientResources.TryGetValue(objectKind, out HashSet<string>? semiTransientResources))
{
SemiTransientResources[objectKind] = semiTransientResources = new(StringComparer.Ordinal);
SemiTransientResources[objectKind] = semiTransientResources = new(StringComparer.OrdinalIgnoreCase);
}
if (!TransientResources.TryGetValue(objectKind, out var resources))
@@ -152,7 +162,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
List<string> newlyAddedGamePaths;
lock (semiTransientResources)
{
newlyAddedGamePaths = transientResources.Except(semiTransientResources, StringComparer.Ordinal).ToList();
newlyAddedGamePaths = transientResources.Except(semiTransientResources, StringComparer.OrdinalIgnoreCase).ToList();
foreach (var gamePath in transientResources)
{
semiTransientResources.Add(gamePath);
@@ -197,12 +207,13 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
public void RemoveTransientResource(ObjectKind objectKind, string path)
{
var normalizedPath = NormalizeGamePath(path);
if (SemiTransientResources.TryGetValue(objectKind, out var resources))
{
resources.RemoveWhere(f => string.Equals(path, f, StringComparison.Ordinal));
resources.Remove(normalizedPath);
if (objectKind == ObjectKind.Player)
{
PlayerConfig.RemovePath(path, objectKind);
PlayerConfig.RemovePath(normalizedPath, objectKind);
Logger.LogTrace("Saving transient.json from {method}", nameof(RemoveTransientResource));
_configurationService.Save();
}
@@ -211,16 +222,17 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
internal bool AddTransientResource(ObjectKind objectKind, string item)
{
if (SemiTransientResources.TryGetValue(objectKind, out var semiTransient) && semiTransient != null && semiTransient.Contains(item))
var normalizedItem = NormalizeGamePath(item);
if (SemiTransientResources.TryGetValue(objectKind, out var semiTransient) && semiTransient != null && semiTransient.Contains(normalizedItem))
return false;
if (!TransientResources.TryGetValue(objectKind, out HashSet<string>? transientResource))
{
transientResource = new HashSet<string>(StringComparer.Ordinal);
transientResource = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
TransientResources[objectKind] = transientResource;
}
return transientResource.Add(item.ToLowerInvariant());
return transientResource.Add(normalizedItem);
}
internal void ClearTransientPaths(ObjectKind objectKind, List<string> list)
@@ -285,33 +297,13 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
private void DalamudUtil_FrameworkUpdate()
{
RefreshPlayerRelatedAddressMap();
lock (_cacheAdditionLock)
{
_cachedHandledPaths.Clear();
}
var activeDescriptors = new Dictionary<nint, ObjectKind>();
foreach (var descriptor in _actorObjectService.ObjectDescriptors)
{
if (TryResolveObjectKind(descriptor, out var resolvedKind))
{
activeDescriptors[descriptor.Address] = resolvedKind;
}
}
foreach (var address in _cachedFrameAddresses.Keys.ToList())
{
if (!activeDescriptors.ContainsKey(address))
{
_cachedFrameAddresses.TryRemove(address, out _);
}
}
foreach (var descriptor in activeDescriptors)
{
_cachedFrameAddresses[descriptor.Key] = descriptor.Value;
}
if (_lastClassJobId != _dalamudUtil.ClassJobId)
{
_lastClassJobId = _dalamudUtil.ClassJobId;
@@ -323,7 +315,9 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData);
SemiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? []).ToHashSet(StringComparer.OrdinalIgnoreCase);
PlayerConfig.JobSpecificPetCache.TryGetValue(_dalamudUtil.ClassJobId, out var petSpecificData);
SemiTransientResources[ObjectKind.Pet] = [.. petSpecificData ?? []];
SemiTransientResources[ObjectKind.Pet] = new HashSet<string>(
petSpecificData ?? [],
StringComparer.OrdinalIgnoreCase);
}
foreach (var kind in Enum.GetValues(typeof(ObjectKind)).Cast<ObjectKind>())
@@ -340,9 +334,12 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
_ = Task.Run(() =>
{
Logger.LogDebug("Penumbra Mod Settings changed, verifying SemiTransientResources");
foreach (var item in _playerRelatedPointers)
lock (_playerRelatedLock)
{
Mediator.Publish(new TransientResourceChangedMessage(item.Address));
foreach (var item in _playerRelatedPointers)
{
Mediator.Publish(new TransientResourceChangedMessage(item.Address));
}
}
});
}
@@ -352,22 +349,24 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
_semiTransientResources = null;
}
private static bool TryResolveObjectKind(ActorObjectService.ActorDescriptor descriptor, out ObjectKind resolvedKind)
private void RefreshPlayerRelatedAddressMap()
{
if (descriptor.OwnedKind is ObjectKind ownedKind)
_playerRelatedByAddress.Clear();
var updatedFrameAddresses = new ConcurrentDictionary<nint, ObjectKind>();
lock (_playerRelatedLock)
{
resolvedKind = ownedKind;
return true;
foreach (var handler in _playerRelatedPointers)
{
var address = (nint)handler.Address;
if (address != nint.Zero)
{
_playerRelatedByAddress[address] = handler;
updatedFrameAddresses[address] = handler.ObjectKind;
}
}
}
if (descriptor.ObjectKind == DalamudObjectKind.Player)
{
resolvedKind = ObjectKind.Player;
return true;
}
resolvedKind = default;
return false;
_cachedFrameAddresses = updatedFrameAddresses;
}
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
@@ -375,18 +374,15 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
if (descriptor.IsInGpose)
return;
if (!TryResolveObjectKind(descriptor, out var resolvedKind))
if (descriptor.OwnedKind is not ObjectKind ownedKind)
return;
if (Logger.IsEnabled(LogLevel.Debug))
{
Logger.LogDebug("ActorObject tracked: {kind} addr={address:X} name={name}", resolvedKind, descriptor.Address, descriptor.Name);
Logger.LogDebug("ActorObject tracked: {kind} addr={address:X} name={name}", ownedKind, descriptor.Address, descriptor.Name);
}
_cachedFrameAddresses[descriptor.Address] = resolvedKind;
if (descriptor.OwnedKind is not ObjectKind ownedKind)
return;
_cachedFrameAddresses[descriptor.Address] = ownedKind;
lock (_ownedHandlerLock)
{
@@ -465,53 +461,84 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
}
}
private static string NormalizeGamePath(string path)
{
if (string.IsNullOrEmpty(path))
return string.Empty;
return path.Replace("\\", "/", StringComparison.Ordinal).ToLowerInvariant();
}
private static string NormalizeFilePath(string path)
{
if (string.IsNullOrEmpty(path))
return string.Empty;
if (path.StartsWith("|", StringComparison.Ordinal))
{
var lastPipe = path.LastIndexOf('|');
if (lastPipe >= 0 && lastPipe + 1 < path.Length)
{
path = path[(lastPipe + 1)..];
}
}
return NormalizeGamePath(path);
}
private static bool HasHandledFileType(string gamePath, string[] handledTypes)
{
for (var i = 0; i < handledTypes.Length; i++)
{
if (gamePath.EndsWith(handledTypes[i], StringComparison.Ordinal))
return true;
}
return false;
}
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
{
var gamePath = msg.GamePath.ToLowerInvariant();
var gameObjectAddress = msg.GameObject;
var filePath = msg.FilePath;
// ignore files already processed this frame
if (_cachedHandledPaths.Contains(gamePath)) return;
lock (_cacheAdditionLock)
if (!_cachedFrameAddresses.TryGetValue(gameObjectAddress, out var objectKind))
{
_cachedHandledPaths.Add(gamePath);
if (_actorObjectService.TryGetOwnedKind(gameObjectAddress, out var ownedKind))
{
objectKind = ownedKind;
}
else
{
return;
}
}
// 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))
var gamePath = NormalizeGamePath(msg.GamePath);
if (string.IsNullOrEmpty(gamePath))
{
return;
}
// ignore files already processed this frame
lock (_cacheAdditionLock)
{
if (!_cachedHandledPaths.Add(gamePath))
{
return;
}
}
// ignore files to not handle
var handledTypes = IsTransientRecording ? _handledRecordingFileTypes.Concat(_handledFileTypes) : _handledFileTypes;
if (!handledTypes.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
var handledTypes = IsTransientRecording ? _handledFileTypesWithRecording : _handledFileTypes;
if (!HasHandledFileType(gamePath, handledTypes))
{
lock (_cacheAdditionLock)
{
_cachedHandledPaths.Add(gamePath);
}
return;
}
// ignore files not belonging to anything player related
if (!_cachedFrameAddresses.TryGetValue(gameObjectAddress, out var objectKind))
var filePath = NormalizeFilePath(msg.FilePath);
// ignore files that are the same
if (string.Equals(filePath, gamePath, StringComparison.Ordinal))
{
lock (_cacheAdditionLock)
{
_cachedHandledPaths.Add(gamePath);
}
return;
}
@@ -523,15 +550,15 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
TransientResources[objectKind] = transientResources;
}
var owner = _playerRelatedPointers.FirstOrDefault(f => f.Address == gameObjectAddress);
_playerRelatedByAddress.TryGetValue(gameObjectAddress, out var owner);
bool alreadyTransient = false;
bool transientContains = transientResources.Contains(replacedGamePath);
bool semiTransientContains = SemiTransientResources.SelectMany(k => k.Value).Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase));
bool transientContains = transientResources.Contains(gamePath);
bool semiTransientContains = SemiTransientResources.Values.Any(value => value.Contains(gamePath));
if (transientContains || semiTransientContains)
{
if (!IsTransientRecording)
Logger.LogTrace("Not adding {replacedPath} => {filePath}, Reason: Transient: {contains}, SemiTransient: {contains2}", replacedGamePath, filePath,
Logger.LogTrace("Not adding {replacedPath} => {filePath}, Reason: Transient: {contains}, SemiTransient: {contains2}", gamePath, filePath,
transientContains, semiTransientContains);
alreadyTransient = true;
}
@@ -539,10 +566,10 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
{
if (!IsTransientRecording)
{
bool isAdded = transientResources.Add(replacedGamePath);
bool isAdded = transientResources.Add(gamePath);
if (isAdded)
{
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath);
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", gamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath);
SendTransients(gameObjectAddress, objectKind);
}
}
@@ -550,7 +577,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
if (owner != null && IsTransientRecording)
{
_recordedTransients.Add(new TransientRecord(owner, replacedGamePath, filePath, alreadyTransient) { AddTransient = !alreadyTransient });
_recordedTransients.Add(new TransientRecord(owner, gamePath, filePath, alreadyTransient) { AddTransient = !alreadyTransient });
}
}
@@ -622,7 +649,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
if (!item.AddTransient || item.AlreadyTransient) continue;
if (!TransientResources.TryGetValue(item.Owner.ObjectKind, out var transient))
{
TransientResources[item.Owner.ObjectKind] = transient = [];
TransientResources[item.Owner.ObjectKind] = transient = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
Logger.LogTrace("Adding recorded: {gamePath} => {filePath}", item.GamePath, item.FilePath);