Files
FlaxEngine/Source/Engine/Content/Storage/FlaxStorage.h
2024-04-22 22:53:27 +02:00

491 lines
14 KiB
C++

// 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;
/// <summary>
/// Flax assets storage container.
/// </summary>
class FLAXENGINE_API FlaxStorage : public Object
{
friend FlaxStorageReference;
friend class BinaryAssetFactoryBase;
friend class BinaryAsset;
public:
/// <summary>
/// Magic code stored in file header to identify contents.
/// </summary>
static const int32 MagicCode;
/// <summary>
/// The custom file data.
/// </summary>
struct CustomData
{
int32 ContentKey;
int32 NotUsed0;
int32 NotUsed1;
int32 NotUsed2;
};
/// <summary>
/// Asset entry info.
/// </summary>
struct Entry
{
/// <summary>
/// The asset identifier.
/// </summary>
Guid ID;
/// <summary>
/// The asset type name identifier (cached in entry for faster per asset info query)
/// </summary>
String TypeName;
/// <summary>
/// The address of the AssetHeader in the file.
/// </summary>
uint32 Address;
/// <summary>
/// Initializes a new instance of the <see cref="Entry"/> struct.
/// </summary>
Entry()
: ID(Guid::Empty)
, Address(0)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Entry"/> struct.
/// </summary>
/// <param name="id">The asset identifier.</param>
/// <param name="typeName">The asset type name identifier.</param>
/// <param name="address">The address in the file.</param>
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<FileReadStream*> _file;
Array<FlaxChunk*> _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:
/// <summary>
/// Finalizes an instance of the <see cref="FlaxStorage"/> class.
/// </summary>
virtual ~FlaxStorage();
public:
/// <summary>
/// Locks the storage chunks data to prevent disposing them. Also ensures that file handles won't be closed while chunks are locked.
/// </summary>
FORCE_INLINE void LockChunks()
{
Platform::InterlockedIncrement(&_chunksLock);
}
/// <summary>
/// Unlocks the storage chunks data.
/// </summary>
FORCE_INLINE void UnlockChunks()
{
Platform::InterlockedDecrement(&_chunksLock);
}
/// <summary>
/// Locks storage data via LockChunks/UnlockChunks. Prevents from releasing chunks data cache.
/// </summary>
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;
}
};
/// <summary>
/// Locks the storage container chunks within a code block section (using a local variable that keeps a scope lock).
/// </summary>
/// <returns>The scope lock.</returns>
LockData Lock()
{
return LockData(this);
}
/// <summary>
/// Locks the storage container chunks within a code block section (using a local variable that keeps a scope lock).
/// </summary>
/// <remarks>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).</remarks>
/// <returns>The scope lock.</returns>
LockData LockSafe();
public:
/// <summary>
/// Gets the references count.
/// </summary>
uint32 GetRefCount() const;
/// <summary>
/// Checks if storage container should be disposed (it's not used anymore).
/// </summary>
bool ShouldDispose() const;
/// <summary>
/// Gets the path.
/// </summary>
FORCE_INLINE const String& GetPath() const
{
return _path;
}
/// <summary>
/// Determines whether this instance is loaded.
/// </summary>
FORCE_INLINE bool IsLoaded() const
{
return _version != 0;
}
/// <summary>
/// Determines whether this instance is disposed.
/// </summary>
FORCE_INLINE bool IsDisposed() const
{
return _version == 0;
}
/// <summary>
/// Gets total memory used by chunks (in bytes).
/// </summary>
uint32 GetMemoryUsage() const;
/// <summary>
/// Determines whether this storage container is a package.
/// </summary>
virtual bool IsPackage() const = 0;
/// <summary>
/// Checks whenever storage container allows the data modifications.
/// </summary>
virtual bool AllowDataModifications() const = 0;
/// <summary>
/// Determines whether the specified asset exists in this container.
/// </summary>
/// <param name="id">The asset identifier.</param>
/// <returns>True if the specified asset exists in this container, otherwise false.</returns>
virtual bool HasAsset(const Guid& id) const = 0;
/// <summary>
/// Determines whether the specified asset exists in this container.
/// </summary>
/// <param name="info">The asset info.</param>
/// <returns>True if the specified asset exists in this container, otherwise false.</returns>
virtual bool HasAsset(const AssetInfo& info) const = 0;
/// <summary>
/// Gets amount of entries in the storage.
/// </summary>
virtual int32 GetEntriesCount() const = 0;
/// <summary>
/// Gets the asset entry at given index.
/// </summary>
/// <param name="index">The asset index.</param>
/// <returns>The output.</returns>
Entry GetEntry(int32 index) const
{
Entry result;
GetEntry(index, result);
return result;
}
/// <summary>
/// Gets the asset entry at given index.
/// </summary>
/// <param name="index">The asset index.</param>
/// <param name="output">The output.</param>
virtual void GetEntry(int32 index, Entry& output) const = 0;
/// <summary>
/// Gets all the entries in the storage.
/// </summary>
/// <param name="output">The output.</param>
virtual void GetEntries(Array<Entry>& output) const = 0;
/// <summary>
/// Gets all the chunks in the storage.
/// </summary>
/// <param name="output">The output.</param>
FORCE_INLINE void GetChunks(Array<FlaxChunk*>& output) const
{
output.Add(_chunks);
}
public:
/// <summary>
/// Loads package from the file.
/// </summary>
/// <returns>True if cannot load, otherwise false</returns>
bool Load();
#if USE_EDITOR
/// <summary>
/// Action fired on storage reloading.
/// </summary>
Delegate<FlaxStorage*> OnReloading;
/// <summary>
/// Action fired on storage reloading. bool param means 'wasReloadFail'
/// </summary>
Delegate<FlaxStorage*, bool> OnReloaded;
/// <summary>
/// Reloads this storage container.
/// </summary>
/// <returns>True if cannot load, otherwise false</returns>
bool Reload();
#endif
/// <summary>
/// Loads the asset header.
/// </summary>
/// <param name="index">The asset index.</param>
/// <param name="data">The data.</param>
/// <returns>True if cannot load data, otherwise false</returns>
bool LoadAssetHeader(const int32 index, AssetInitData& data)
{
return LoadAssetHeader(GetEntry(index), data);
}
/// <summary>
/// Loads the asset header.
/// </summary>
/// <param name="id">The asset identifier.</param>
/// <param name="data">The data.</param>
/// <returns>True if cannot load data, otherwise false</returns>
bool LoadAssetHeader(const Guid& id, AssetInitData& data);
/// <summary>
/// Loads the asset chunk.
/// </summary>
/// <param name="chunk">The chunk.</param>
/// <returns>True if cannot load data, otherwise false</returns>
bool LoadAssetChunk(FlaxChunk* chunk);
#if USE_EDITOR
/// <summary>
/// Changes the asset identifier. Warning! Changes only ID in storage layer, not dependencies resolving at all!
/// </summary>
/// <param name="e">The asset entry.</param>
/// <param name="newId">The new identifier.</param>
/// <returns>True if cannot change data, otherwise false</returns>
bool ChangeAssetID(Entry& e, const Guid& newId);
#endif
/// <summary>
/// Allocates the new chunk within a storage container (if it's possible).
/// </summary>
/// <returns>Allocated chunk or null if cannot.</returns>
FlaxChunk* AllocateChunk();
/// <summary>
/// Closes the file handles (it can be modified from the outside).
/// </summary>
bool CloseFileHandles();
/// <summary>
/// Releases storage resources and closes handle to the file.
/// </summary>
virtual void Dispose();
public:
/// <summary>
/// Ticks this instance.
/// </summary>
void Tick();
#if USE_EDITOR
void OnRename(const StringView& newPath);
#endif
public:
#if USE_EDITOR
/// <summary>
/// Saves the specified asset data to the storage container.
/// </summary>
/// <param name="data">The data to save.</param>
/// <param name="silentMode">In silent mode don't reload opened storage container that is using target file.</param>
/// <returns>True if cannot save, otherwise false</returns>
bool Save(AssetInitData& data, bool silentMode = false);
/// <summary>
/// Creates new FlaxFile using specified asset data.
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="data">The data to write.</param>
/// <param name="silentMode">In silent mode don't reload opened storage container that is using target file.</param>
/// <param name="customData">Custom options.</param>
/// <returns>True if cannot create package, otherwise false</returns>
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);
}
/// <summary>
/// Creates new FlaxFile using specified assets data.
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="data">The data to write.</param>
/// <param name="silentMode">In silent mode don't reload opened storage container that is using target file.</param>
/// <param name="customData">Custom options.</param>
/// <returns>True if cannot create package, otherwise false</returns>
FORCE_INLINE static bool Create(const StringView& path, const Array<AssetInitData>& data, bool silentMode = false, const CustomData* customData = nullptr)
{
return Create(path, data.Get(), data.Count(), silentMode, customData);
}
/// <summary>
/// Creates new FlaxFile using specified assets data.
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="data">The data to write.</param>
/// <param name="dataCount">The data size.</param>
/// <param name="silentMode">In silent mode don't reload opened storage container that is using target file.</param>
/// <param name="customData">Custom options.</param>
/// <returns>True if cannot create package, otherwise false</returns>
static bool Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode = false, const CustomData* customData = nullptr);
/// <summary>
/// Creates new FlaxFile using specified assets data.
/// </summary>
/// <param name="stream">The output stream.</param>
/// <param name="data">The data to write.</param>
/// <param name="dataCount">The data size.</param>
/// <param name="customData">Custom options.</param>
/// <returns>True if cannot create package, otherwise false</returns>
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;
};