From c6204fc27427d20bfcd0d5a9d85170f7231801bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 31 Mar 2026 12:03:18 +0200 Subject: [PATCH] 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). --- Source/Engine/Content/BinaryAsset.cpp | 5 ++ Source/Engine/Content/BinaryAsset.h | 1 + Source/Engine/Content/Content.cpp | 56 ++++++++++++++++++- Source/Engine/Content/Content.h | 6 ++ .../Content/Loading/Tasks/LoadAssetTask.h | 2 - 5 files changed, 67 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Content/BinaryAsset.cpp b/Source/Engine/Content/BinaryAsset.cpp index eb5aff817..f583ab32a 100644 --- a/Source/Engine/Content/BinaryAsset.cpp +++ b/Source/Engine/Content/BinaryAsset.cpp @@ -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 diff --git a/Source/Engine/Content/BinaryAsset.h b/Source/Engine/Content/BinaryAsset.h index 39c34588e..e52e9dcff 100644 --- a/Source/Engine/Content/BinaryAsset.h +++ b/Source/Engine/Content/BinaryAsset.h @@ -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 diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 59d2dfffa..3ec43ac5a 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -94,6 +94,9 @@ namespace DateTime LastWorkspaceDiscovery; CriticalSection WorkspaceDiscoveryLocker; #endif +#if USE_EDITOR + Dictionary> 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(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 diff --git a/Source/Engine/Content/Content.h b/Source/Engine/Content/Content.h index e286e7c7d..f55f5f9c0 100644 --- a/Source/Engine/Content/Content.h +++ b/Source/Engine/Content/Content.h @@ -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 diff --git a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h index 4eae2829b..e7d4077fd 100644 --- a/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h +++ b/Source/Engine/Content/Loading/Tasks/LoadAssetTask.h @@ -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(); } } };