// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Object.h" #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Serialization/FileReadStream.h" #include "Engine/Threading/ThreadLocal.h" #include "FlaxChunk.h" #include "AssetHeader.h" #include "../AssetInfo.h" class ContentStorageManager; struct FlaxStorageReference; /// /// Flax assets storage container. /// class FLAXENGINE_API FlaxStorage : public Object { friend FlaxStorageReference; friend class BinaryAssetFactoryBase; friend class BinaryAsset; public: /// /// Magic code stored in file header to identify contents. /// static const int32 MagicCode; /// /// The custom file data. /// struct CustomData { int32 ContentKey; int32 NotUsed0; int32 NotUsed1; int32 NotUsed2; }; /// /// Asset entry info. /// struct Entry { /// /// The asset identifier. /// Guid ID; /// /// The asset type name identifier (cached in entry for faster per asset info query) /// String TypeName; /// /// The address of the AssetHeader in the file. /// uint32 Address; /// /// Initializes a new instance of the struct. /// Entry() : ID(Guid::Empty) , Address(0) { } /// /// Initializes a new instance of the struct. /// /// The asset identifier. /// The asset type name identifier. /// The address in the file. Entry(const Guid& id, const StringView& typeName, uint32 address) : ID(id) , TypeName(typeName) , Address(address) { } }; protected: // State int64 _refCount; int64 _chunksLock; double _lastRefLostTime; CriticalSection _loadLocker; // Storage ThreadLocal _file; Array _chunks; // Metadata uint32 _version; String _path; protected: explicit FlaxStorage(const StringView& path); private: // Used by FlaxStorageReference: void AddRef() { Platform::InterlockedIncrement(&_refCount); } void RemoveRef() { Platform::InterlockedDecrement(&_refCount); if (Platform::AtomicRead(&_refCount) == 0) { _lastRefLostTime = Platform::GetTimeSeconds(); } } public: /// /// Finalizes an instance of the class. /// virtual ~FlaxStorage(); public: /// /// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked. /// FORCE_INLINE void LockChunks() { Platform::InterlockedIncrement(&_chunksLock); } /// /// Unlocks the storage chunks data. /// FORCE_INLINE void UnlockChunks() { Platform::InterlockedDecrement(&_chunksLock); } /// /// Locks storage data via LockChunks/UnlockChunks. Prevents from releasing chunks data cache. /// struct LockData { friend FlaxStorage; static LockData Invalid; private: FlaxStorage* _storage; LockData(FlaxStorage* storage) : _storage(storage) { if (_storage) _storage->LockChunks(); } public: LockData(const LockData& other) : _storage(other._storage) { if (_storage) _storage->LockChunks(); } LockData(LockData&& other) noexcept : _storage(other._storage) { other._storage = nullptr; } ~LockData() { if (_storage) _storage->UnlockChunks(); } void Release() { if (_storage) { _storage->UnlockChunks(); _storage = nullptr; } } LockData& operator=(const LockData& other) { if (this != &other) { if (_storage) _storage->UnlockChunks(); _storage = other._storage; if (_storage) _storage->LockChunks(); } return *this; } LockData& operator=(LockData&& other) noexcept { if (this == &other) return *this; _storage = other._storage; other._storage = nullptr; return *this; } }; /// /// Locks the storage container chunks within a code block section (using a local variable that keeps a scope lock). /// /// The scope lock. LockData Lock() { return LockData(this); } /// /// Locks the storage container chunks within a code block section (using a local variable that keeps a scope lock). /// /// Lock is done with a safe critical section for a ContentStorageManager that prevents to minor issues with threading. Should be used on a separate threads (eg. GameCooker thread). /// The scope lock. LockData LockSafe(); public: /// /// Gets the references count. /// uint32 GetRefCount() const; /// /// Checks if storage container should be disposed (it's not used anymore). /// bool ShouldDispose() const; /// /// Gets the path. /// FORCE_INLINE const String& GetPath() const { return _path; } /// /// Determines whether this instance is loaded. /// FORCE_INLINE bool IsLoaded() const { return _version != 0; } /// /// Determines whether this instance is disposed. /// FORCE_INLINE bool IsDisposed() const { return _version == 0; } /// /// Gets total memory used by chunks (in bytes). /// uint32 GetMemoryUsage() const; /// /// Determines whether this storage container is a package. /// virtual bool IsPackage() const = 0; /// /// Checks whenever storage container allows the data modifications. /// virtual bool AllowDataModifications() const = 0; /// /// Determines whether the specified asset exists in this container. /// /// The asset identifier. /// True if the specified asset exists in this container, otherwise false. virtual bool HasAsset(const Guid& id) const = 0; /// /// Determines whether the specified asset exists in this container. /// /// The asset info. /// True if the specified asset exists in this container, otherwise false. virtual bool HasAsset(const AssetInfo& info) const = 0; /// /// Gets amount of entries in the storage. /// virtual int32 GetEntriesCount() const = 0; /// /// Gets the asset entry at given index. /// /// The asset index. /// The output. Entry GetEntry(int32 index) const { Entry result; GetEntry(index, result); return result; } /// /// Gets the asset entry at given index. /// /// The asset index. /// The output. virtual void GetEntry(int32 index, Entry& output) const = 0; /// /// Gets all the entries in the storage. /// /// The output. virtual void GetEntries(Array& output) const = 0; /// /// Gets all the chunks in the storage. /// /// The output. FORCE_INLINE void GetChunks(Array& output) const { output.Add(_chunks); } public: /// /// Loads package from the file. /// /// True if cannot load, otherwise false bool Load(); #if USE_EDITOR /// /// Action fired on storage reloading. /// Delegate OnReloading; /// /// Action fired on storage reloading. bool param means 'wasReloadFail' /// Delegate OnReloaded; /// /// Reloads this storage container. /// /// True if cannot load, otherwise false bool Reload(); #endif /// /// Loads the asset header. /// /// The asset index. /// The data. /// True if cannot load data, otherwise false bool LoadAssetHeader(const int32 index, AssetInitData& data) { return LoadAssetHeader(GetEntry(index), data); } /// /// Loads the asset header. /// /// The asset identifier. /// The data. /// True if cannot load data, otherwise false bool LoadAssetHeader(const Guid& id, AssetInitData& data); /// /// Loads the asset chunk. /// /// The chunk. /// True if cannot load data, otherwise false bool LoadAssetChunk(FlaxChunk* chunk); #if USE_EDITOR /// /// Changes the asset identifier. Warning! Changes only ID in storage layer, not dependencies resolving at all! /// /// The asset entry. /// The new identifier. /// True if cannot change data, otherwise false bool ChangeAssetID(Entry& e, const Guid& newId); #endif /// /// Allocates the new chunk within a storage container (if it's possible). /// /// Allocated chunk or null if cannot. FlaxChunk* AllocateChunk(); /// /// Closes the file handles (it can be modified from the outside). /// bool CloseFileHandles(); /// /// Releases storage resources and closes handle to the file. /// virtual void Dispose(); public: /// /// Ticks this instance. /// void Tick(); #if USE_EDITOR void OnRename(const StringView& newPath); #endif public: #if USE_EDITOR /// /// Saves the specified asset data to the storage container. /// /// The data to save. /// In silent mode don't reload opened storage container that is using target file. /// True if cannot save, otherwise false bool Save(AssetInitData& data, bool silentMode = false); /// /// Creates new FlaxFile using specified asset data. /// /// The file path. /// The data to write. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& data, bool silentMode = false, const CustomData* customData = nullptr) { return Create(path, &data, 1, silentMode, customData); } /// /// Creates new FlaxFile using specified assets data. /// /// The file path. /// The data to write. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false FORCE_INLINE static bool Create(const StringView& path, const Array& data, bool silentMode = false, const CustomData* customData = nullptr) { return Create(path, data.Get(), data.Count(), silentMode, customData); } /// /// Creates new FlaxFile using specified assets data. /// /// The file path. /// The data to write. /// The data size. /// In silent mode don't reload opened storage container that is using target file. /// Custom options. /// True if cannot create package, otherwise false static bool Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode = false, const CustomData* customData = nullptr); /// /// Creates new FlaxFile using specified assets data. /// /// The output stream. /// The data to write. /// The data size. /// Custom options. /// True if cannot create package, otherwise false static bool Create(WriteStream* stream, const AssetInitData* data, int32 dataCount, const CustomData* customData = nullptr); #endif protected: bool LoadAssetHeader(const Entry& e, AssetInitData& data); void AddChunk(FlaxChunk* chunk); virtual void AddEntry(Entry& e) = 0; FileReadStream* OpenFile(); virtual bool GetEntry(const Guid& id, Entry& e) = 0; };