/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/Editor/EditorFramework/Assets/AssetCurator.h
527 строк
23 KB
Jan Krassnigg
Fixed #1892: Better error handling for duplicate sub-asset GUIDs (#1944)
25 май 2026, 17:04
Не верифицирован
25 май 2026, 17:04
c23e447
Код
Авторство
О чём код?
#pragma once #include <Core/Configuration/PlatformProfile.h> #include <EditorFramework/Assets/AssetDocumentInfo.h> #include <EditorFramework/Assets/AssetDocumentManager.h> #include <EditorFramework/Assets/Declarations.h> #include <EditorFramework/EditorFrameworkDLL.h> #include <Foundation/Algorithm/HashHelperString.h> #include <Foundation/Application/Config/FileSystemConfig.h> #include <Foundation/Configuration/Singleton.h> #include <Foundation/Containers/HashTable.h> #include <Foundation/IO/DirectoryWatcher.h> #include <Foundation/Logging/LogEntry.h> #include <Foundation/Profiling/Profiling.h> #include <Foundation/Threading/AtomicInteger.h> #include <Foundation/Threading/DelegateTask.h> #include <Foundation/Threading/LockedObject.h> #include <Foundation/Threading/Mutex.h> #include <Foundation/Threading/TaskSystem.h> #include <Foundation/Time/Timestamp.h> #include <ToolsFoundation/Document/DocumentManager.h> #include <ToolsFoundation/FileSystem/DataDirPath.h> #include <ToolsFoundation/FileSystem/Declarations.h> #include <tuple> class ezUpdateTask; class ezTask; class ezAssetDocumentManager; class ezDirectoryWatcher; struct ezFileStats; class ezAssetProcessorLog; class ezFileSystemWatcher; class ezAssetTableWriter; struct ezFileChangedEvent; class ezFileSystemModel; class ezObjectAccessorBase; #if 0 // Define to enable extensive curator profile scopes # define CURATOR_PROFILE(szName) EZ_PROFILE_SCOPE(szName) #else # define CURATOR_PROFILE(Name) #endif /// Custom mutex that allows to profile the time in the curator lock. class ezCuratorMutex : public ezMutex { public: void Lock() { CURATOR_PROFILE("ezCuratorMutex"); ezMutex::Lock(); } void Unlock() { ezMutex::Unlock(); } }; struct EZ_EDITORFRAMEWORK_DLL ezAssetInfo { ezAssetInfo() = default; void Update(ezUniquePtr<ezAssetInfo>& rhs); ezAssetDocumentManager* GetManager() { return static_cast<ezAssetDocumentManager*>(m_pDocumentTypeDescriptor->m_pManager); } enum TransformState : ezUInt8 { Unknown = 0, UpToDate, NeedsImport, NeedsTransform, NeedsThumbnail, TransformError, MissingTransformDependency, MissingThumbnailDependency, MissingPackageDependency, CircularDependency, COUNT, }; ezUInt8 m_LastStateUpdate = 0; ///< Changes every time m_TransformState is modified. Used to detect stale computations done outside the lock. ezAssetExistanceState::Enum m_ExistanceState = ezAssetExistanceState::FileAdded; TransformState m_TransformState = TransformState::Unknown; ezUInt64 m_AssetHash = 0; ///< Valid if m_TransformState != Unknown and asset not in Curator's m_TransformStateStale list. ezUInt64 m_ThumbHash = 0; ///< Valid if m_TransformState != Unknown and asset not in Curator's m_TransformStateStale list. ezUInt64 m_PackageHash = 0; ///< Valid if m_TransformState != Unknown and asset not in Curator's m_TransformStateStale list. ezDynamicArray<ezLogEntry> m_LogEntries; const ezAssetDocumentTypeDescriptor* m_pDocumentTypeDescriptor = nullptr; ezDataDirPath m_Path; ezUniquePtr<ezAssetDocumentInfo> m_Info; ezSet<ezString> m_MissingTransformDeps; ezSet<ezString> m_MissingThumbnailDeps; ezSet<ezString> m_MissingPackageDeps; ezSet<ezString> m_CircularDependencies; ezSet<ezUuid> m_SubAssets; ///< Main asset uses the same GUID as this (see m_Info), but is NOT stored in m_SubAssets private: EZ_DISALLOW_COPY_AND_ASSIGN(ezAssetInfo); }; /// Information about an asset or sub-asset. struct EZ_EDITORFRAMEWORK_DLL ezSubAsset { ezStringView GetName() const; void GetSubAssetIdentifier(ezStringBuilder& out_sPath) const; ezAssetExistanceState::Enum m_ExistanceState = ezAssetExistanceState::FileAdded; ezAssetInfo* m_pAssetInfo = nullptr; ezTime m_LastAccess; bool m_bMainAsset = true; ezSubAssetData m_Data; }; struct ezAssetCuratorEvent { enum class Type { AssetAdded, AssetRemoved, AssetMoved, AssetUpdated, AssetListReset, ActivePlatformChanged, }; ezUuid m_AssetGuid; const ezSubAsset* m_pInfo; Type m_Type; }; class EZ_EDITORFRAMEWORK_DLL ezAssetCurator { EZ_DECLARE_SINGLETON(ezAssetCurator); public: ezAssetCurator(); ~ezAssetCurator(); /// \name Setup ///@{ /// Starts init task. Need to call WaitForInitialize to finish before loading docs. void StartInitialize(const ezApplicationFileSystemConfig& cfg); /// Waits for init task to finish. void WaitForInitialize(); void Deinitialize(); void MainThreadTick(bool bTopLevel); ///@} /// \name Asset Platform Configurations ///@{ public: /// The main platform on which development happens. E.g. "Default". /// /// TODO: review this concept const ezPlatformProfile* GetDevelopmentAssetProfile() const; /// The currently active target platform for asset processing. const ezPlatformProfile* GetActiveAssetProfile() const; /// Returns the index of the currently active asset platform configuration ezUInt32 GetActiveAssetProfileIndex() const; /// Returns ezInvalidIndex if no config with the given name exists. Name comparison is case insensitive. ezUInt32 FindAssetProfileByName(const char* szPlatform); ezUInt32 GetNumAssetProfiles() const; /// Always returns a valid config. E.g. even if ezInvalidIndex is passed in, it will fall back to the default config (at index 0). const ezPlatformProfile* GetAssetProfile(ezUInt32 uiIndex) const; /// Always returns a valid config. E.g. even if ezInvalidIndex is passed in, it will fall back to the default config (at index 0). ezPlatformProfile* GetAssetProfile(ezUInt32 uiIndex); /// Adds a new profile. The name should be set afterwards to a unique name. ezPlatformProfile* CreateAssetProfile(); /// Deletes the given asset profile, if possible. /// /// The function fails when the given profile is the main profile (at index 0), /// or it is the currently active profile. ezResult DeleteAssetProfile(ezPlatformProfile* pProfile); /// Switches the currently active asset target platform. /// /// Broadcasts ezAssetCuratorEvent::Type::ActivePlatformChanged on change. void SetActiveAssetProfileByIndex(ezUInt32 uiIndex, bool bForceReevaluation = false); /// Saves the current asset configurations. Returns failure if the output file could not be written to. ezResult SaveAssetProfiles(); void SaveRuntimeProfiles(); ///@} /// \name Asset Reference Replacement ///@{ /// Result of a bulk asset reference replacement operation. struct ReplaceAssetResult { ezUInt32 m_uiDocumentsModified = 0; ezUInt32 m_uiDocumentsFailed = 0; ezUInt32 m_uiPropertiesReplaced = 0; ezDynamicArray<ezString> m_Errors; }; /// Recursively replaces asset references in all properties of a document object and its children. /// /// \return The number of properties that were successfully replaced. ezUInt32 ReplaceAssetReferenceInObject(ezObjectAccessorBase* pAccessor, const ezDocumentObject* pObject, ezStringView sOldReference, ezStringView sNewReference, ezDynamicArray<ezString>& out_errors); /// Replaces all asset references in a document, wrapped in a transaction. /// /// \return The number of properties that were successfully replaced. ezUInt32 ReplaceAssetReferenceInDocument(ezDocument* pDocument, ezStringView sOldReference, ezStringView sNewReference, ezDynamicArray<ezString>& out_errors); /// Replaces asset references in all documents that directly use the specified asset. /// /// Opens each referencing document, replaces all occurrences, and saves it. /// Documents are opened without a window. Errors are collected but do not abort the operation. ReplaceAssetResult ReplaceAssetReferenceInUses(ezUuid assetToReplace, ezStringView sOldReference, ezStringView sNewReference); private: void ClearAssetProfiles(); void SetupDefaultAssetProfiles(); ezResult LoadAssetProfiles(); void ComputeAllDocumentManagerAssetProfileHashes(); ezHybridArray<ezPlatformProfile*, 8> m_AssetProfiles; ///@} /// \name High Level Functions ///@{ public: ezDateTime GetLastFullTransformDate() const; void StoreFullTransformDate(); /// Transforms all assets and writes the lookup tables. If the given platform is empty, the active platform is used. /// /// Pass ezTransformFlags::TriggeredManually to make sure that all assets get transformed, /// even the ones that should not be transformed by background processors (mainly scenes). ezStatus TransformAllAssets(ezBitflags<ezTransformFlags> transformFlags = ezTransformFlags::TriggeredManually, const ezPlatformProfile* pAssetProfile = nullptr); ezTransformStatus TransformAsset(const ezUuid& assetGuid, ezBitflags<ezTransformFlags> transformFlags, const ezPlatformProfile* pAssetProfile = nullptr); ezTransformStatus CreateThumbnail(const ezUuid& assetGuid); void ResaveAllAssets(ezStringView sPrefixPath); /// Some assets are not automatically updated by the asset dependency detection (mainly Collections) because of their transitive data dependencies. /// So we must update them when the user does something 'significant' like doing TransformAllAssets or a scene export. void TransformAssetsForSceneExport(const ezPlatformProfile* pAssetProfile = nullptr); /// Writes the asset lookup table for the given platform, or the currently active platform if nullptr is passed. ezResult WriteAssetTables(const ezPlatformProfile* pAssetProfile = nullptr, bool bForce = false); ///@} /// \name Asset Access ///@{ using ezLockedSubAsset = ezLockedObject<ezMutex, const ezSubAsset>; /// Tries to find the asset information for an asset identified through a string. /// /// The string may be a stringyfied asset GUID or a relative or absolute path. The function will try all possibilities. /// If no asset can be found, an empty/invalid ezAssetInfo is returned. /// If bExhaustiveSearch is set the function will go through all known assets and find the closest match. const ezLockedSubAsset FindSubAsset(ezStringView sPathOrGuid, bool bExhaustiveSearch = false) const; /// Same as GetAssteInfo, but wraps the return value into a ezLockedSubAsset struct const ezLockedSubAsset GetSubAsset(const ezUuid& assetGuid) const; using ezLockedSubAssetTable = ezLockedObject<ezMutex, const ezHashTable<ezUuid, ezSubAsset>>; /// Returns the table of all known assets in a locked structure const ezLockedSubAssetTable GetKnownSubAssets() const; using ezLockedAssetTable = ezLockedObject<ezMutex, const ezHashTable<ezUuid, ezAssetInfo*>>; /// Returns the table of all known assets in a locked structure const ezLockedAssetTable GetKnownAssets() const; /// Computes the transform hash for the asset and its transform dependencies. Returns 0 if anything went wrong. ezUInt64 GetAssetTransformHash(ezUuid assetGuid); /// Computes the thumbnail hash for the asset and its thumbnail dependencies. Returns 0 if anything went wrong. ezUInt64 GetAssetThumbnailHash(ezUuid assetGuid); ezAssetInfo::TransformState IsAssetUpToDate(const ezUuid& assetGuid, const ezPlatformProfile* pAssetProfile, const ezAssetDocumentTypeDescriptor* pTypeDescriptor, ezUInt64& out_uiAssetHash, ezUInt64& out_uiThumbHash, ezUInt64& out_uiPackageHash, bool bForce = false); /// Returns the number of assets in the system and how many are in what transform state void GetAssetTransformStats(ezUInt32& out_uiNumAssets, ezHybridArray<ezUInt32, ezAssetInfo::TransformState::COUNT>& out_count); /// Iterates over all known data directories and returns the absolute path to the directory in which this asset is located ezString FindDataDirectoryForAsset(ezStringView sAbsoluteAssetPath) const; /// Collects all main asset GUIDs located within the specified folder path. /// /// Searches through all known assets and adds the string representation of their GUID /// to the output array if their absolute path starts with the given folder path. /// Only main assets are included (not sub-assets). void GetAllAssetsInFolder(ezStringView sFolderPath, ezDynamicArray<ezString>& out_assetGuids) const; /// Uses knowledge about all existing files on disk to find the best match for a file. Very slow. /// /// \param sFile /// File name (may include a path) to search for. Will be modified both on success and failure to give a 'reasonable' result. ezResult FindBestMatchForFile(ezStringBuilder& ref_sFile, ezArrayPtr<ezString> allowedFileExtensions) const; /// Finds all uses, either as transform or thumbnail dependencies to a given asset. /// /// Technically this finds all dependencies to this asset but in practice there are no uses of transform dependencies between assets right now so the result is a list of thumbnail dependencies and can be referred to as simply a list of asset references. /// /// \param assetGuid /// The asset to find use cases for. /// \param uses /// List of assets that use 'assetGuid'. Any previous content of the set is not removed. /// \param transitive /// If set, will also find indirect uses of the asset. void FindAllUses(ezUuid assetGuid, ezSet<ezUuid>& ref_uses, bool bTransitive) const; /// Returns all assets that use a file for transform. Use this to e.g. figure which assets still reference a .tga file in the project. /// \param sAbsolutePath Absolute path to any file inside a data directory. /// \param ref_uses List of assets that use 'sAbsolutePath'. Any previous content of the set is not removed. void FindAllUses(ezStringView sAbsolutePath, ezSet<ezUuid>& ref_uses) const; /// Returns whether a file is referenced, i.e. used for transforming an asset. Use this to e.g. figure out whether a .tga file is still in use by any asset. /// \param sAbsolutePath Absolute path to any file inside a data directory. /// \return True, if at least one asset references the given file. bool IsReferenced(ezStringView sAbsolutePath) const; ///@} /// \name Manual and Automatic Change Notification ///@{ /// Allows to tell the system of a new or changed file, that might be of interest to the Curator. void NotifyOfFileChange(ezStringView sAbsolutePath); /// Allows to tell the system to re-evaluate an assets status. void NotifyOfAssetChange(const ezUuid& assetGuid); void UpdateAssetLastAccessTime(const ezUuid& assetGuid); /// Checks file system for any changes. Call in case the file system watcher does not pick up certain changes. void CheckFileSystem(); void NeedsReloadResources(const ezUuid& assetGuid) const; void InvalidateAssetsWithTransformState(ezAssetInfo::TransformState state); ///@} /// \name Utilities ///@{ /// Generates one transitive hull for all the dependencies that are enabled. The set will contain dependencies that are reachable via any combination of enabled reference types. void GenerateTransitiveHull(const ezStringView sAssetOrPath, ezSet<ezString>& inout_deps, ezBitflags<ezDependencyFlags> dependencyTypes) const; /// \brief Generates one transitive hull for all the asset dependencies that are enabled. The set will contain dependencies that are reachable via any combination of enabled reference types. void GenerateTransitiveAssetHull(const ezUuid& assetGuid, ezSet<ezUuid>& inout_deps, ezBitflags<ezDependencyFlags> dependencyTypes); /// \brief Copies each value of deps into out_SettingsHashMap and fills the value of assets with the hash value of the dependencyType. For files, the file hash is used. void GenerateSettingsHashMap(const ezSet<ezString>& deps, ezBitflags<ezDependencyFlags> dependencyType, ezMap<ezString, ezUInt64>& out_settingsHashMap) const; /// Generates one inverse transitive hull for all the types dependencies that are enabled. The set will contain inverse dependencies that can reach the given asset (pAssetInfo) via any combination of the enabled reference types. As only assets can have dependencies, the inverse hull is always just asset GUIDs. void GenerateInverseTransitiveHull(const ezAssetInfo* pAssetInfo, ezSet<ezUuid>& inout_inverseDeps, bool bIncludeTransformDeps = false, bool bIncludeThumbnailDeps = false) const; /// Generates a DGML graph of all transform and thumbnail dependencies. void WriteDependencyDGML(const ezUuid& guid, ezStringView sOutputFile) const; struct ExportResult { ezUInt32 m_uiCopiedFiles = 0; ezUInt32 m_uiFailedFiles = 0; }; /// Exports assets and their dependencies to a destination folder. /// /// Takes an array of source paths (asset GUIDs as strings or file paths) and exports them /// along with all their dependencies to the destination folder. Files are copied preserving /// their relative paths from the data directories. Each file is copied only once, even if /// it appears in multiple dependency trees. ExportResult ExportAssets(ezArrayPtr<ezString> sources, ezStringView sDestinationFolder, ezBitflags<ezDependencyFlags> includeDependencyTypes = ezDependencyFlags::Transform | ezDependencyFlags::Thumbnail | ezDependencyFlags::Package) const; ///@} public: ezEvent<const ezAssetCuratorEvent&> m_Events; private: /// \name Processing ///@{ ezTransformStatus ProcessAsset(ezAssetInfo* pAssetInfo, const ezPlatformProfile* pAssetProfile, ezBitflags<ezTransformFlags> transformFlags); ezStatus ResaveAsset(ezAssetInfo* pAssetInfo); /// Returns the asset info for the asset with the given GUID or nullptr if no such asset exists. ezAssetInfo* GetAssetInfo(const ezUuid& assetGuid); const ezAssetInfo* GetAssetInfo(const ezUuid& assetGuid) const; ezSubAsset* GetSubAssetInternal(const ezUuid& assetGuid); /// Returns the asset info for the asset with the given (stringyfied) GUID or nullptr if no such asset exists. ezAssetInfo* GetAssetInfo(const ezString& sAssetGuid); void OnFileChangedEvent(const ezFileChangedEvent& e); /// Some assets are vital for the engine to run. Each data directory can contain a [DataDirName].ezCollectionAsset /// that has all its references transformed before any other documents are loaded. void ProcessAllCoreAssets(); ///@} /// \name Update Task ///@{ void RestartUpdateTask(); void ShutdownUpdateTask(); bool GetNextAssetToUpdate(ezUuid& out_guid, ezStringBuilder& out_sAbsPath); void OnUpdateTaskFinished(const ezSharedPtr<ezTask>& pTask); void RunNextUpdateTask(); ///@} /// \name Asset Hashing and Status Updates (AssetUpdates.cpp) ///@{ bool AddAssetHash(ezString& sPath, ezBitflags<ezDependencyFlags> dependencyType, ezUInt64& out_AssetHash, ezUInt64& out_ThumbHash, ezUInt64& out_PackageHash, bool bForce); ezAssetInfo::TransformState HashAsset(ezUInt64 uiSettingsHash, const ezHybridArray<ezString, 16>& assetTransformDeps, const ezHybridArray<ezString, 16>& assetThumbnailDeps, const ezHybridArray<ezString, 16>& assetPackageDeps, ezSet<ezString>& missingTransformDeps, ezSet<ezString>& missingThumbnailDeps, ezSet<ezString>& missingPackageDeps, ezUInt64& out_AssetHash, ezUInt64& out_ThumbHash, ezUInt64& out_PackageHash, bool bForce); ezResult EnsureAssetInfoUpdated(const ezDataDirPath& absFilePath, const ezFileStatus& stat, bool bForce = false); void TrackDependencies(ezAssetInfo* pAssetInfo); void UntrackDependencies(ezAssetInfo* pAssetInfo); ezResult CheckForCircularDependencies(ezAssetInfo* pAssetInfo); void UpdateTrackedFiles(const ezUuid& assetGuid, const ezSet<ezString>& files, ezMap<ezString, ezHybridArray<ezUuid, 1>>& inverseTracker, ezSet<std::tuple<ezUuid, ezUuid>>& unresolved, bool bAdd); void UpdateUnresolvedTrackedFiles(ezMap<ezString, ezHybridArray<ezUuid, 1>>& inverseTracker, ezSet<std::tuple<ezUuid, ezUuid>>& unresolved); ezResult ReadAssetDocumentInfo(const ezDataDirPath& absFilePath, const ezFileStatus& stat, ezUniquePtr<ezAssetInfo>& assetInfo); ezResult UpdateSubAssets(ezAssetInfo& assetInfo); void RemoveAssetTransformState(const ezUuid& assetGuid); void InvalidateAssetTransformState(const ezUuid& assetGuid); ezAssetInfo::TransformState UpdateAssetTransformState(ezUuid assetGuid, ezUInt64& out_AssetHash, ezUInt64& out_ThumbHash, ezUInt64& out_PackageHash, bool bForce); void UpdateAssetTransformState(const ezUuid& assetGuid, ezAssetInfo::TransformState state); void UpdateAssetTransformLog(const ezUuid& assetGuid, ezDynamicArray<ezLogEntry>& logEntries); void SetAssetExistanceState(ezAssetInfo& assetInfo, ezAssetExistanceState::Enum state); ///@} /// \name Check File System Helper ///@{ void SetAllAssetStatusUnknown(); void LoadCaches(ezMap<ezDataDirPath, ezFileStatus, ezCompareDataDirPath>& out_referencedFiles, ezMap<ezDataDirPath, ezFileStatus::Status, ezCompareDataDirPath>& out_referencedFolders); void SaveCaches(const ezMap<ezDataDirPath, ezFileStatus, ezCompareDataDirPath>& referencedFiles, const ezMap<ezDataDirPath, ezFileStatus::Status, ezCompareDataDirPath>& referencedFolders); static void BuildFileExtensionSet(ezSet<ezString>& AllExtensions); ///@} /// \name Utilities ///@{ public: /// Deletes all files in all asset caches, except for the asset outputs that exceed the threshold. /// /// -> OutputReliability::Perfect -> deletes everything /// -> OutputReliability::Good -> keeps the 'Perfect' files /// -> OutputReliability::Unknown -> keeps the 'Good' and 'Perfect' files void ClearAssetCaches(ezAssetDocumentManager::OutputReliability threshold); ///@} private: friend class ezUpdateTask; friend class ezAssetProcessor; friend class ezEditorProcessorProcess; mutable ezCuratorMutex m_CuratorMutex; // Global lock ezTaskGroupID m_InitializeCuratorTaskID; ezUInt32 m_uiActiveAssetProfile = 0; // Actual data stored in the curator ezHashTable<ezUuid, ezAssetInfo*> m_KnownAssets; ezHashTable<ezUuid, ezSubAsset> m_KnownSubAssets; // Derived dependency lookup tables ezMap<ezString, ezHybridArray<ezUuid, 1>> m_InverseTransformDeps; // [Absolute path -> asset Guid] ezMap<ezString, ezHybridArray<ezUuid, 1>> m_InverseThumbnailDeps; // [Absolute path -> asset Guid] ezSet<std::tuple<ezUuid, ezUuid>> m_UnresolvedTransformDeps; ///< If a dependency wasn't known yet when an asset info was loaded, it is put in here. ezSet<std::tuple<ezUuid, ezUuid>> m_UnresolvedThumbnailDeps; // State caches ezHashSet<ezUuid> m_TransformState[ezAssetInfo::TransformState::COUNT]; ezHashSet<ezUuid> m_SubAssetChanged; ///< Flushed in main thread tick ezHashSet<ezUuid> m_TransformStateStale; ezHashSet<ezUuid> m_Updating; // Serialized cache mutable ezCuratorMutex m_CachedAssetsMutex; ///< Only locks m_CachedAssets ezMap<ezString, ezUniquePtr<ezAssetDocumentInfo>> m_CachedAssets; ezMap<ezString, ezFileStatus> m_CachedFiles; // Immutable data after StartInitialize ezApplicationFileSystemConfig m_FileSystemConfig; ezUniquePtr<ezAssetTableWriter> m_pAssetTableWriter; ezSet<ezString> m_ValidAssetExtensions; // Update task bool m_bRunUpdateTask = false; ezSharedPtr<ezUpdateTask> m_pUpdateTask; ezTaskGroupID m_UpdateTaskGroup; }; class ezUpdateTask final : public ezTask { public: ezUpdateTask(ezOnTaskFinishedCallback onTaskFinished); ~ezUpdateTask(); private: ezStringBuilder m_sAssetPath; virtual void Execute() override; };