// 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;
};