diff --git a/Source/Engine/Content/Cache/AssetsCache.cpp b/Source/Engine/Content/Cache/AssetsCache.cpp
index 96083509e..e36a0d76c 100644
--- a/Source/Engine/Content/Cache/AssetsCache.cpp
+++ b/Source/Engine/Content/Cache/AssetsCache.cpp
@@ -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
}
diff --git a/Source/Engine/Content/Cache/AssetsCache.h b/Source/Engine/Content/Cache/AssetsCache.h
index 42f05a2aa..28380b69d 100644
--- a/Source/Engine/Content/Cache/AssetsCache.h
+++ b/Source/Engine/Content/Cache/AssetsCache.h
@@ -58,6 +58,11 @@ public:
/// The file modified date.
///
DateTime FileModified;
+
+ ///
+ /// True if a warning about this entry being inaccessible has already been logged (prevents log spam). Runtime-only, not serialized.
+ ///
+ bool WarnedInaccessible = false;
#endif
Entry()
@@ -73,6 +78,27 @@ public:
}
};
+ ///
+ /// Result of validating an asset cache entry.
+ ///
+ enum class EntryValidation
+ {
+ ///
+ /// File verified, contains this asset.
+ ///
+ Valid,
+
+ ///
+ /// File missing or contains a different asset.
+ ///
+ Invalid,
+
+ ///
+ /// File exists but cannot be opened (locked by another process).
+ ///
+ Inaccessible,
+ };
+
typedef Dictionary Registry;
typedef Dictionary PathsMapping;
@@ -232,6 +258,6 @@ public:
/// Determines whether cached asset entry is valid.
///
/// The asset entry.
- /// True if is valid, otherwise false.
- bool IsEntryValid(Entry& e);
+ /// The validation result.
+ EntryValidation IsEntryValid(Entry& e);
};