Optimize Asset::GetPath in cooked build

This commit is contained in:
Wojtek Figat
2025-07-29 21:41:59 +02:00
parent b8218e9ab4
commit 6d05bf16b1
12 changed files with 79 additions and 54 deletions

View File

@@ -127,7 +127,7 @@ public:
/// <summary>
/// Gets the path to the asset storage file. In Editor, it reflects the actual file, in cooked Game, it fakes the Editor path to be informative for developers.
/// </summary>
API_PROPERTY() virtual const String& GetPath() const = 0;
API_PROPERTY() virtual StringView GetPath() const = 0;
/// <summary>
/// Gets the asset type name.

View File

@@ -619,7 +619,7 @@ Asset::LoadResult Model::load()
{
String name;
#if !BUILD_RELEASE
name = GetPath() + TEXT(".SDF");
name = String(GetPath()) + TEXT(".SDF");
#endif
SDF.Texture = GPUDevice::Instance->CreateTexture(name);
}

View File

@@ -464,10 +464,10 @@ void BinaryAsset::OnDeleteObject()
#endif
const String& BinaryAsset::GetPath() const
StringView BinaryAsset::GetPath() const
{
#if USE_EDITOR
return Storage ? Storage->GetPath() : String::Empty;
return Storage ? Storage->GetPath() : StringView::Empty;
#else
// In build all assets are packed into packages so use ID for original path lookup
return Content::GetRegistry()->GetEditorAssetPath(_id);

View File

@@ -292,7 +292,7 @@ public:
#if USE_EDITOR
void OnDeleteObject() override;
#endif
const String& GetPath() const final override;
StringView GetPath() const final override;
uint64 GetMemoryUsage() const override;
protected:

View File

@@ -10,12 +10,24 @@
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Engine/Globals.h"
#include "FlaxEngine.Gen.h"
#if ASSETS_CACHE_EDITABLE
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Threading/Threading.h"
#define ASSETS_CACHE_LOCK() ScopeLock lock(_locker)
#else
#define ASSETS_CACHE_LOCK()
#endif
int32 AssetsCache::Size() const
{
ASSETS_CACHE_LOCK();
const int32 result = _registry.Count();
return result;
}
void AssetsCache::Init()
{
@@ -72,7 +84,7 @@ void AssetsCache::Init()
return;
}
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_isDirty = false;
// Load elements count
@@ -127,6 +139,16 @@ void AssetsCache::Init()
_pathsMapping.Add(mappedPath, id);
}
#if !USE_EDITOR && !BUILD_RELEASE
// Build inverse path mapping in development builds for faster GetEditorAssetPath (eg. used by PROFILE_CPU_ASSET)
_pathsMappingInv.Clear();
_pathsMappingInv.EnsureCapacity(count);
for (auto& mapping : _pathsMapping)
{
_pathsMappingInv.Add(mapping.Value, StringView(mapping.Key));
}
#endif
// Check errors
const bool hasError = stream->HasError();
deleteStream.Delete();
@@ -154,7 +176,7 @@ bool AssetsCache::Save()
if (!_isDirty && FileSystem::FileExists(_path))
return false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
if (Save(_path, _registry, _pathsMapping))
return true;
@@ -223,12 +245,16 @@ bool AssetsCache::Save(const StringView& path, const Registry& entries, const Pa
return false;
}
const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
StringView AssetsCache::GetEditorAssetPath(const Guid& id) const
{
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
#if USE_EDITOR
auto e = _registry.TryGet(id);
return e ? e->Info.Path : String::Empty;
#elif !BUILD_RELEASE
StringView result;
_pathsMappingInv.TryGet(id, result);
return result;
#else
for (auto& e : _pathsMapping)
{
@@ -242,10 +268,8 @@ const String& AssetsCache::GetEditorAssetPath(const Guid& id) const
bool AssetsCache::FindAsset(const StringView& path, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has direct mapping to id (used for some cooked assets)
Guid id;
@@ -294,7 +318,7 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
{
PROFILE_CPU();
bool result = false;
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -316,14 +340,14 @@ bool AssetsCache::FindAsset(const Guid& id, AssetInfo& info)
void AssetsCache::GetAll(Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
_registry.GetKeys(result);
}
void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& result) const
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.TypeName == typeName)
@@ -331,6 +355,8 @@ void AssetsCache::GetAllByTypeName(const StringView& typeName, Array<Guid>& resu
}
}
#if ASSETS_CACHE_EDITABLE
void AssetsCache::RegisterAssets(FlaxStorage* storage)
{
PROFILE_CPU();
@@ -342,7 +368,7 @@ void AssetsCache::RegisterAssets(FlaxStorage* storage)
storage->GetEntries(entries);
ASSERT(entries.HasItems());
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
auto storagePath = storage->GetPath();
// Remove all old entries from that location
@@ -440,7 +466,7 @@ void AssetsCache::RegisterAssets(const FlaxStorageReference& storage)
void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const StringView& path)
{
PROFILE_CPU();
ScopeLock lock(_locker);
ASSETS_CACHE_LOCK();
// Check if asset has been already added to the registry
bool isMissing = true;
@@ -492,8 +518,7 @@ void AssetsCache::RegisterAsset(const Guid& id, const String& typeName, const St
bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == path)
@@ -506,16 +531,13 @@ bool AssetsCache::DeleteAsset(const StringView& path, AssetInfo* info)
break;
}
}
_locker.Unlock();
return result;
}
bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
const auto e = _registry.TryGet(id);
if (e != nullptr)
{
@@ -525,16 +547,13 @@ bool AssetsCache::DeleteAsset(const Guid& id, AssetInfo* info)
_isDirty = true;
result = true;
}
_locker.Unlock();
return result;
}
bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPath)
{
bool result = false;
_locker.Lock();
ASSETS_CACHE_LOCK();
for (auto i = _registry.Begin(); i.IsNotEnd(); ++i)
{
if (i->Value.Info.Path == oldPath)
@@ -545,11 +564,11 @@ bool AssetsCache::RenameAsset(const StringView& oldPath, const StringView& newPa
break;
}
}
_locker.Unlock();
return result;
}
#endif
bool AssetsCache::IsEntryValid(Entry& e)
{
#if ENABLE_ASSETS_DISCOVERY

View File

@@ -16,6 +16,9 @@ struct AssetHeader;
struct FlaxStorageReference;
class FlaxStorage;
// In cooked game all assets are there and all access to registry is read-only so can be multithreaded
#define ASSETS_CACHE_EDITABLE (USE_EDITOR)
/// <summary>
/// Assets cache flags.
/// </summary>
@@ -75,22 +78,21 @@ public:
private:
bool _isDirty = false;
#if ASSETS_CACHE_EDITABLE
CriticalSection _locker;
#endif
Registry _registry;
PathsMapping _pathsMapping;
#if !USE_EDITOR && !BUILD_RELEASE
Dictionary<Guid, StringView> _pathsMappingInv;
#endif
String _path;
public:
/// <summary>
/// Gets amount of registered assets.
/// </summary>
int32 Size() const
{
_locker.Lock();
const int32 result = _registry.Count();
_locker.Unlock();
return result;
}
int32 Size() const;
public:
/// <summary>
@@ -116,11 +118,11 @@ public:
public:
/// <summary>
/// Finds the asset path by id. In editor it returns the actual asset path, at runtime it returns the mapped asset path.
/// Finds the asset path by id. In editor, it returns the actual asset path, at runtime it returns the mapped asset path.
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
const String& GetEditorAssetPath(const Guid& id) const;
StringView GetEditorAssetPath(const Guid& id) const;
/// <summary>
/// Finds the asset info by path.
@@ -173,6 +175,7 @@ public:
/// <param name="result">The result array.</param>
void GetAllByTypeName(const StringView& typeName, Array<Guid, HeapAllocation>& result) const;
#if ASSETS_CACHE_EDITABLE
/// <summary>
/// Register assets in the cache
/// </summary>
@@ -223,6 +226,7 @@ public:
/// <param name="newPath">New path</param>
/// <returns>True if has been deleted, otherwise false</returns>
bool RenameAsset(const StringView& oldPath, const StringView& newPath);
#endif
/// <summary>
/// Determines whether cached asset entry is valid.

View File

@@ -521,7 +521,7 @@ bool Content::GetAssetInfo(const StringView& path, AssetInfo& info)
#endif
}
String Content::GetEditorAssetPath(const Guid& id)
StringView Content::GetEditorAssetPath(const Guid& id)
{
return Cache.GetEditorAssetPath(id);
}
@@ -749,6 +749,7 @@ void Content::DeleteAsset(const StringView& path)
return;
}
#if USE_EDITOR
ScopeLock locker(AssetsLocker);
// Remove from registry
@@ -765,6 +766,7 @@ void Content::DeleteAsset(const StringView& path)
// Delete file
deleteFileSafety(path, info.ID);
#endif
}
void Content::deleteFileSafety(const StringView& path, const Guid& id)

View File

@@ -75,7 +75,7 @@ public:
/// </summary>
/// <param name="id">The asset id.</param>
/// <returns>The asset path, or empty if failed to find.</returns>
API_FUNCTION() static String GetEditorAssetPath(const Guid& id);
API_FUNCTION() static StringView GetEditorAssetPath(const Guid& id);
/// <summary>
/// Finds all the asset IDs. Uses asset registry.

View File

@@ -91,7 +91,7 @@ void JsonAssetBase::OnGetData(rapidjson_flax::StringBuffer& buffer) const
Data->Accept(writerObj.GetWriter());
}
const String& JsonAssetBase::GetPath() const
StringView JsonAssetBase::GetPath() const
{
#if USE_EDITOR
return _path;

View File

@@ -88,7 +88,7 @@ protected:
public:
// [Asset]
const String& GetPath() const override;
StringView GetPath() const override;
uint64 GetMemoryUsage() const override;
#if USE_EDITOR
void GetReferences(Array<Guid, HeapAllocation>& assets, Array<String, HeapAllocation>& files) const override;

View File

@@ -36,7 +36,7 @@ public:
// [ContentLoadTask]
String ToString() const override
{
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : String::Empty);
return String::Format(TEXT("Load Asset Data Task ({}, {}, {})"), (int32)GetState(), _chunks, _asset ? _asset->GetPath() : StringView::Empty);
}
bool HasReference(Object* obj) const override
{

View File

@@ -134,7 +134,7 @@ public:
struct Args
{
rapidjson_flax::Value& Data;
const String* AssetPath;
StringView AssetPath;
int32 EngineBuild;
float TimeBudget;
};
@@ -222,7 +222,7 @@ namespace LevelImpl
SceneResult loadScene(SceneLoader& loader, JsonAsset* sceneAsset, float* timeBudget = nullptr);
SceneResult loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene = nullptr, float* timeBudget = nullptr);
SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Document& document, Scene** outScene = nullptr, float* timeBudget = nullptr);
SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, const String* assetPath = nullptr, float* timeBudget = nullptr);
SceneResult loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene = nullptr, StringView assetPath = StringView(), float* timeBudget = nullptr);
bool unloadScene(Scene* scene);
bool unloadScenes();
bool saveScene(Scene* scene);
@@ -959,7 +959,7 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, JsonAsset* sceneAsset, flo
return SceneResult::Failed;
}
return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, &sceneAsset->GetPath(), timeBudget);
return loadScene(loader, *sceneAsset->Data, sceneAsset->DataEngineBuild, nullptr, sceneAsset->GetPath(), timeBudget);
}
SceneResult LevelImpl::loadScene(SceneLoader& loader, const BytesContainer& sceneData, Scene** outScene, float* timeBudget)
@@ -999,7 +999,7 @@ SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Document&
return loadScene(loader, data->value, saveEngineBuild, outScene, nullptr, timeBudget);
}
SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, const String* assetPath, float* timeBudget)
SceneResult LevelImpl::loadScene(SceneLoader& loader, rapidjson_flax::Value& data, int32 engineBuild, Scene** outScene, StringView assetPath, float* timeBudget)
{
PROFILE_CPU_NAMED("Level.LoadScene");
PROFILE_MEM(Level);
@@ -1401,12 +1401,12 @@ SceneResult SceneLoader::OnEnd(Args& args)
LOG(Error, "Failed to resave asset '{}'", prefab->GetPath());
}
}
if (ContentDeprecated::Clear() && args.AssetPath)
if (ContentDeprecated::Clear() && args.AssetPath != StringView())
{
LOG(Info, "Resaving asset '{}' that uses deprecated data format", *args.AssetPath);
if (saveScene(Scene, *args.AssetPath))
LOG(Info, "Resaving asset '{}' that uses deprecated data format", args.AssetPath);
if (saveScene(Scene, args.AssetPath))
{
LOG(Error, "Failed to resave asset '{}'", *args.AssetPath);
LOG(Error, "Failed to resave asset '{}'", args.AssetPath);
}
}
#endif