Fix asset cache eviction for locked files to retain cached info

https://github.com/LOOPDISK/FlaxEngine/pull/45
This commit is contained in:
Murry Lancashire
2026-06-02 15:53:35 +02:00
committed by Wojtek Figat
parent 2531a4b918
commit 44117084c8
2 changed files with 105 additions and 51 deletions
+77 -49
View File
@@ -113,7 +113,7 @@ void AssetsCache::Init()
}
// Use only valid entries
if (IsEntryValid(e))
if (IsEntryValid(e) != EntryValidation::Invalid)
_registry.Add(e.Info.ID, e);
else
rejectedCount++;
@@ -295,14 +295,23 @@ bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
auto& e = i->Value;
if (e.Info.Path == path)
{
if (!IsEntryValid(e))
const auto validation = IsEntryValid(e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
_registry.Remove(i);
}
else
{
// Found
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e.WarnedInaccessible)
{
e.WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e.Info.Path, e.Info.ID, e.Info.TypeName);
}
#endif
// Found valid or inaccessible but return cached info either way
result = true;
info = e.Info;
}
@@ -322,13 +331,22 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
auto e = _registry.TryGet(id);
if (e != nullptr)
{
if (!IsEntryValid(*e))
const auto validation = IsEntryValid(*e);
if (validation == EntryValidation::Invalid)
{
LOG(Warning, "Missing file from registry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
_registry.Remove(id);
}
else
{
#if ENABLE_ASSETS_DISCOVERY
if (validation == EntryValidation::Inaccessible && !e->WarnedInaccessible)
{
e->WarnedInaccessible = true;
LOG(Warning, "Asset file locked, keeping cached entry: \'{0}\':{1}:{2}", e->Info.Path, e->Info.ID, e->Info.TypeName);
}
#endif
// Found
result = true;
info = e->Info;
@@ -567,60 +585,70 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
#endif
bool AssetsCache::IsEntryValid(Entry& e)
AssetsCache::EntryValidation AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY
// Check if file exists
if (FileSystem::FileExists(e.Info.Path))
if (!FileSystem::FileExists(e.Info.Path))
return EntryValidation::Invalid;
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
{
// Check if file hasn't been modified
const auto fileModified = FileSystem::GetFileLastEditTime(e.Info.Path);
if (fileModified == e.FileModified)
return true;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
_isDirty = true;
return isValid;
}
}
e.WarnedInaccessible = false;
return EntryValidation::Valid;
}
return false;
const auto extension = FileSystem::GetExtension(e.Info.Path).ToLower();
// Check if it's a binary asset
if (ContentStorageManager::IsFlaxStorageExtension(extension))
{
// Validate ID within storage container
const auto storage = ContentStorageManager::GetStorage(e.Info.Path);
if (storage)
{
// Check if storage at given location contains that asset
const bool isValid = storage->HasAsset(e.Info);
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
// Check for json resource
else if (JsonStorageProxy::IsValidExtension(extension))
{
// Check Json storage layer
Guid jsonId;
String jsonTypeName;
if (JsonStorageProxy::GetAssetInfo(e.Info.Path, jsonId, jsonTypeName))
{
const bool isValid = e.Info.ID == jsonId && e.Info.TypeName == jsonTypeName;
// Update entry and mark cache as dirty
e.FileModified = fileModified;
e.WarnedInaccessible = false;
_isDirty = true;
return isValid ? EntryValidation::Valid : EntryValidation::Invalid;
}
}
else
{
// Unknown file type
return EntryValidation::Invalid;
}
// File exists but cannot be read (likely locked by git or another process)
return EntryValidation::Inaccessible;
#else
// In game we don't care about it because all cached asset entries are valid (precached)
// Skip only entries with missing file
return e.Info.Path.HasChars();
return e.Info.Path.HasChars() ? EntryValidation::Valid : EntryValidation::Invalid;
#endif
}
+28 -2
View File
@@ -58,6 +58,11 @@ public:
/// The file modified date.
/// </summary>
DateTime FileModified;
/// <summary>
/// True if a warning about this entry being inaccessible has already been logged (prevents log spam). Runtime-only, not serialized.
/// </summary>
bool WarnedInaccessible = false;
#endif
Entry()
@@ -73,6 +78,27 @@ public:
}
};
/// <summary>
/// Result of validating an asset cache entry.
/// </summary>
enum class EntryValidation
{
/// <summary>
/// File verified, contains this asset.
/// </summary>
Valid,
/// <summary>
/// File missing or contains a different asset.
/// </summary>
Invalid,
/// <summary>
/// File exists but cannot be opened (locked by another process).
/// </summary>
Inaccessible,
};
typedef Dictionary<Guid, Entry> Registry;
typedef Dictionary<String, Guid> PathsMapping;
@@ -232,6 +258,6 @@ public:
/// Determines whether cached asset entry is valid.
/// </summary>
/// <param name="e">The asset entry.</param>
/// <returns>True if is valid, otherwise false.</returns>
bool IsEntryValid(Entry& e);
/// <returns>The validation result.</returns>
EntryValidation IsEntryValid(Entry& e);
};