Fix binary asset dependencies tracking when dependent asset gets loaded later on

#3951

Adds additional cache for dependencies tracking to update them when other asset gets loaded later on.
Remove critical-section from `LoadAssetTask` that was causing some deadlocks (data access is already atomic there).
This commit is contained in:
Wojtek Figat
2026-03-31 12:03:18 +02:00
parent 8b383d4dbe
commit c6204fc274
5 changed files with 67 additions and 3 deletions

View File

@@ -84,6 +84,11 @@ bool BinaryAsset::Init(AssetInitData& initData)
{
asset->_dependantAssets.Add(this);
}
else
{
// Dependency is not yet loaded to keep track this link to act when it's loaded
Content::onAssetDepend(this, e.First);
}
}
#endif

View File

@@ -22,6 +22,7 @@
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API BinaryAsset : public Asset
{
DECLARE_ASSET_HEADER(BinaryAsset);
friend Content;
protected:
AssetHeader _header;
FlaxStorageReference _storageRef; // Allow asset to have missing storage reference but only before asset is loaded or if it's virtual

View File

@@ -94,6 +94,9 @@ namespace
DateTime LastWorkspaceDiscovery;
CriticalSection WorkspaceDiscoveryLocker;
#endif
#if USE_EDITOR
Dictionary<Guid, HashSet<BinaryAsset*>> PendingDependencies;
#endif
}
#if ENABLE_ASSETS_DISCOVERY
@@ -157,6 +160,9 @@ void ContentService::Update()
{
auto asset = LoadedAssetsToInvoke.Dequeue();
asset->onLoaded_MainThread();
#if USE_EDITOR
Content::onAddDependencies(asset);
#endif
}
}
@@ -1027,10 +1033,17 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
FileSystem::DeleteFile(tmpPath);
// Reload storage
if (auto storage = ContentStorageManager::GetStorage(dstPath, false))
auto storage = ContentStorageManager::GetStorage(dstPath, false);
if (storage && storage->IsLoaded())
{
storage->Reload();
}
else if (auto dependencies = PendingDependencies.TryGet(dstId))
{
// Destination storage is not loaded but there are other assets that depend on it so update them
for (const auto& e : *dependencies)
e.Item->OnDependencyModified(nullptr);
}
}
}
else
@@ -1218,6 +1231,9 @@ void Content::tryCallOnLoaded(Asset* asset)
{
LoadedAssetsToInvoke.RemoveAtKeepOrder(index);
asset->onLoaded_MainThread();
#if USE_EDITOR
onAddDependencies(asset);
#endif
}
}
@@ -1235,6 +1251,10 @@ void Content::onAssetUnload(Asset* asset)
Assets.Remove(asset->GetID());
UnloadQueue.Remove(asset);
LoadedAssetsToInvoke.Remove(asset);
#if USE_EDITOR
for (auto& e : PendingDependencies)
e.Value.Remove(asset);
#endif
}
void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId)
@@ -1242,8 +1262,42 @@ void Content::onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId
ScopeLock locker(AssetsLocker);
Assets.Remove(oldId);
Assets.Add(newId, asset);
#if USE_EDITOR
if (PendingDependencies.ContainsKey(oldId))
{
auto deps = MoveTemp(PendingDependencies[oldId]);
PendingDependencies.Remove(oldId);
PendingDependencies.Add(newId, MoveTemp(deps));
}
#endif
}
#if USE_EDITOR
void Content::onAssetDepend(BinaryAsset* asset, const Guid& otherId)
{
ScopeLock locker(AssetsLocker);
PendingDependencies[otherId].Add(asset);
}
void Content::onAddDependencies(Asset* asset)
{
auto it = PendingDependencies.Find(asset->GetID());
if (it.IsNotEnd())
{
auto& dependencies = it->Value;
auto binaryAsset = Asset::Cast<BinaryAsset>(asset);
if (binaryAsset)
{
for (const auto& e : dependencies)
binaryAsset->_dependantAssets.Add(e.Item);
}
PendingDependencies.Remove(it);
}
}
#endif
bool Content::IsAssetTypeIdInvalid(const ScriptingTypeHandle& type, const ScriptingTypeHandle& assetType)
{
// Skip if no restrictions for the type

View File

@@ -389,6 +389,12 @@ private:
static void onAssetLoaded(Asset* asset);
static void onAssetUnload(Asset* asset);
static void onAssetChangeId(Asset* asset, const Guid& oldId, const Guid& newId);
#if USE_EDITOR
friend class BinaryAsset;
friend class ContentService;
static void onAssetDepend(BinaryAsset* asset, const Guid& otherId);
static void onAddDependencies(Asset* asset);
#endif
static void deleteFileSafety(const StringView& path, const Guid& id);
// Internal bindings

View File

@@ -80,7 +80,6 @@ private:
auto asset = Asset.Get();
if (asset)
{
asset->Locker.Lock();
Task* task = (Task*)Platform::AtomicRead(&asset->_loadingTask);
if (task)
{
@@ -99,7 +98,6 @@ private:
task = task->GetContinueWithTask();
} while (task);
}
asset->Locker.Unlock();
}
}
};