Fix packaging issues to properly read asset data after it's serialized with a new format
This commit is contained in:
@@ -368,11 +368,21 @@ bool CookAssetsStep::Process(CookingData& data, CacheData& cache, Asset* asset)
|
||||
// Virtual assets are not included into the build
|
||||
return false;
|
||||
}
|
||||
const bool wasLoaded = asset->IsLoaded();
|
||||
if (asset->WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Failed to load asset \'{0}\'", asset->ToString());
|
||||
return true;
|
||||
}
|
||||
if (!wasLoaded)
|
||||
{
|
||||
// HACK: give some time to resave any old assets in Asset::onLoad after it's loaded
|
||||
// This assumes that if Load Thread enters Asset::Save then it will get asset lock and hold it until asset is saved
|
||||
// So we can take the same lock to wait for save end but first we need to wait for it to get that lock
|
||||
// (in future try to handle it in a better way)
|
||||
Platform::Sleep(5);
|
||||
}
|
||||
ScopeLock lock(asset->Locker);
|
||||
|
||||
// Switch based on an asset type
|
||||
const auto asBinaryAsset = dynamic_cast<BinaryAsset*>(asset);
|
||||
|
||||
@@ -372,6 +372,7 @@ bool BinaryAsset::SaveToAsset(const StringView& path, AssetInitData& data, bool
|
||||
const auto locks = storage->_chunksLock;
|
||||
storage->_chunksLock = 0;
|
||||
result = storage->Save(data, silentMode);
|
||||
ASSERT(storage->_chunksLock == 0);
|
||||
storage->_chunksLock = locks;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -20,7 +20,7 @@ typedef uint16 AssetChunksFlag;
|
||||
#define ALL_ASSET_CHUNKS MAX_uint16
|
||||
|
||||
/// <summary>
|
||||
/// Flax Asset header
|
||||
/// Flax asset file header.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API AssetHeader
|
||||
{
|
||||
@@ -51,34 +51,6 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the chunks.
|
||||
/// </summary>
|
||||
/// <param name="output">The output data.</param>
|
||||
template<typename AllocationType = HeapAllocation>
|
||||
void GetChunks(Array<FlaxChunk*, AllocationType>& output) const
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr)
|
||||
output.Add(Chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chunks that are loaded.
|
||||
/// </summary>
|
||||
/// <param name="output">The output data.</param>
|
||||
template<typename AllocationType = HeapAllocation>
|
||||
void GetLoadedChunks(Array<FlaxChunk*, AllocationType>& output) const
|
||||
{
|
||||
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
|
||||
{
|
||||
if (Chunks[i] != nullptr && Chunks[i]->IsLoaded())
|
||||
output.Add(Chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of created asset chunks.
|
||||
/// </summary>
|
||||
@@ -95,14 +67,13 @@ public:
|
||||
void UnlinkChunks();
|
||||
|
||||
/// <summary>
|
||||
/// Gets string with a human-readable info about that header
|
||||
/// Gets string with a human-readable info about that header.
|
||||
/// </summary>
|
||||
/// <returns>Header info string</returns>
|
||||
String ToString() const;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Flax Asset header data
|
||||
/// Flax asset header with data.
|
||||
/// </summary>
|
||||
struct FLAXENGINE_API AssetInitData
|
||||
{
|
||||
@@ -137,12 +108,5 @@ public:
|
||||
/// <summary>
|
||||
/// Gets the hash code.
|
||||
/// </summary>
|
||||
uint32 GetHashCode() const
|
||||
{
|
||||
// Note: do not use Metadata/Dependencies because it may not be loaded (it's optional)
|
||||
uint32 hashCode = GetHash(Header.ID);
|
||||
hashCode = (hashCode * 397) ^ SerializedVersion;
|
||||
hashCode = (hashCode * 397) ^ CustomData.Length();
|
||||
return hashCode;
|
||||
}
|
||||
uint32 GetHashCode() const;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,10 @@ public:
|
||||
bool HasAsset(const Guid& id) const override;
|
||||
bool HasAsset(const AssetInfo& info) const override;
|
||||
int32 GetEntriesCount() const override;
|
||||
void GetEntry(int32 index, Entry& output) const override;
|
||||
void GetEntry(int32 index, Entry& value) const override;
|
||||
#if USE_EDITOR
|
||||
void SetEntry(int32 index, const Entry& value) override;
|
||||
#endif
|
||||
void GetEntries(Array<Entry>& output) const override;
|
||||
void Dispose() override;
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ public:
|
||||
bool HasAsset(const Guid& id) const override;
|
||||
bool HasAsset(const AssetInfo& info) const override;
|
||||
int32 GetEntriesCount() const override;
|
||||
void GetEntry(int32 index, Entry& output) const override;
|
||||
void GetEntry(int32 index, Entry& value) const override;
|
||||
#if USE_EDITOR
|
||||
void SetEntry(int32 index, const Entry& value) override;
|
||||
#endif
|
||||
void GetEntries(Array<Entry>& output) const override;
|
||||
void Dispose() override;
|
||||
|
||||
|
||||
@@ -49,6 +49,15 @@ String AssetHeader::ToString() const
|
||||
return String::Format(TEXT("ID: {0}, TypeName: {1}, Chunks Count: {2}"), ID, TypeName, GetChunksCount());
|
||||
}
|
||||
|
||||
uint32 AssetInitData::GetHashCode() const
|
||||
{
|
||||
// Do not use Metadata/Dependencies because it may not be loaded (it's optional)
|
||||
uint32 hashCode = GetHash(Header.ID);
|
||||
hashCode = (hashCode * 397) ^ SerializedVersion;
|
||||
hashCode = (hashCode * 397) ^ CustomData.Length();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
void FlaxChunk::RegisterUsage()
|
||||
{
|
||||
LastAccessTime = Platform::GetTimeSeconds();
|
||||
@@ -605,6 +614,65 @@ bool FlaxStorage::Reload()
|
||||
return failed;
|
||||
}
|
||||
|
||||
bool FlaxStorage::ReloadSilent()
|
||||
{
|
||||
ScopeLock lock(_loadLocker);
|
||||
if (!IsLoaded())
|
||||
return false;
|
||||
|
||||
// Open file
|
||||
auto stream = OpenFile();
|
||||
if (stream == nullptr)
|
||||
return true;
|
||||
|
||||
// Magic Code
|
||||
uint32 magicCode;
|
||||
stream->ReadUint32(&magicCode);
|
||||
if (magicCode != MagicCode)
|
||||
{
|
||||
LOG(Warning, "Invalid asset magic code in {0}", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Version
|
||||
uint32 version;
|
||||
stream->ReadUint32(&version);
|
||||
if (version == 9)
|
||||
{
|
||||
_version = version;
|
||||
|
||||
// Custom storage data
|
||||
CustomData customData;
|
||||
stream->Read(customData);
|
||||
|
||||
// Entries
|
||||
int32 assetsCount;
|
||||
stream->ReadInt32(&assetsCount);
|
||||
if (assetsCount != GetEntriesCount())
|
||||
{
|
||||
LOG(Warning, "Incorrect amount of assets ({} instead of {}) saved in {}", assetsCount, GetEntriesCount(), ToString());
|
||||
return true;
|
||||
}
|
||||
for (int32 i = 0; i < assetsCount; i++)
|
||||
{
|
||||
SerializedEntryV9 se;
|
||||
stream->ReadBytes(&se, sizeof(se));
|
||||
Entry e(se.ID, se.TypeName.Data, se.Address);
|
||||
SetEntry(i, e);
|
||||
}
|
||||
|
||||
// TODO: load chunks metadata? compare against loaded chunks?
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback to full reload
|
||||
LOG(Warning, "Fallback to full reload of {0}", ToString());
|
||||
Reload();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool FlaxStorage::LoadAssetHeader(const Guid& id, AssetInitData& data)
|
||||
@@ -809,7 +877,7 @@ FlaxChunk* FlaxStorage::AllocateChunk()
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode, const CustomData* customData)
|
||||
bool FlaxStorage::Create(const StringView& path, Span<AssetInitData> assets, bool silentMode, const CustomData* customData)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
ZoneText(*path, path.Length());
|
||||
@@ -824,24 +892,32 @@ bool FlaxStorage::Create(const StringView& path, const AssetInitData* data, int3
|
||||
return true;
|
||||
|
||||
// Create package
|
||||
bool result = Create(stream, data, dataCount, customData);
|
||||
bool result = Create(stream, assets, customData);
|
||||
|
||||
// Close file
|
||||
Delete(stream);
|
||||
|
||||
// Reload storage container (only if not in silent mode)
|
||||
if (storage && !silentMode)
|
||||
// Reload storage container
|
||||
if (storage && storage->IsLoaded())
|
||||
{
|
||||
storage->Reload();
|
||||
if (silentMode)
|
||||
{
|
||||
// Silent data-only reload (loaded chunks location in file has been updated)
|
||||
storage->ReloadSilent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full reload
|
||||
storage->Reload();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 dataCount, const CustomData* customData)
|
||||
bool FlaxStorage::Create(WriteStream* stream, Span<AssetInitData> assets, const CustomData* customData)
|
||||
{
|
||||
// Validate inputs
|
||||
if (data == nullptr || dataCount <= 0)
|
||||
if (assets.Get() == nullptr || assets.Length() <= 0)
|
||||
{
|
||||
LOG(Warning, "Cannot create new package. No assets to write.");
|
||||
return true;
|
||||
@@ -849,27 +925,31 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
|
||||
// Prepare data
|
||||
Array<SerializedEntryV9, InlinedAllocation<1>> entries;
|
||||
entries.Resize(dataCount);
|
||||
entries.Resize(assets.Length());
|
||||
Array<FlaxChunk*> chunks;
|
||||
|
||||
// Get all chunks
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
data[i].Header.GetLoadedChunks(chunks);
|
||||
int32 chunksCount = chunks.Count();
|
||||
for (const AssetInitData& asset : assets)
|
||||
{
|
||||
for (FlaxChunk* chunk : asset.Header.Chunks)
|
||||
{
|
||||
if (chunk && chunk->IsLoaded())
|
||||
chunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
const int32 chunksCount = chunks.Count();
|
||||
|
||||
// TODO: sort chunks by size? smaller ones first?
|
||||
// Calculate start address of the first asset header location
|
||||
// 0 -> Header -> Entries Count -> Entries -> Chunks Count -> Chunk Locations
|
||||
int32 currentAddress = sizeof(Header)
|
||||
+ sizeof(int32)
|
||||
+ sizeof(SerializedEntryV9) * dataCount
|
||||
+ sizeof(SerializedEntryV9) * assets.Length()
|
||||
+ sizeof(int32)
|
||||
+ (sizeof(FlaxChunk::Location) + sizeof(int32)) * chunksCount;
|
||||
|
||||
// Initialize entries offsets in the file
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
for (int32 i = 0; i < assets.Length(); i++)
|
||||
{
|
||||
auto& asset = data[i];
|
||||
const AssetInitData& asset = assets[i];
|
||||
entries[i] = SerializedEntryV9(asset.Header.ID, asset.Header.TypeName, currentAddress);
|
||||
|
||||
// Move forward by asset header data size
|
||||
@@ -934,8 +1014,8 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
stream->Write(mainHeader);
|
||||
|
||||
// Write asset entries
|
||||
stream->WriteInt32(dataCount);
|
||||
stream->WriteBytes(entries.Get(), sizeof(SerializedEntryV9) * dataCount);
|
||||
stream->WriteInt32(assets.Length());
|
||||
stream->WriteBytes(entries.Get(), sizeof(SerializedEntryV9) * assets.Length());
|
||||
|
||||
// Write chunk locations and meta
|
||||
stream->WriteInt32(chunksCount);
|
||||
@@ -948,20 +1028,18 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
}
|
||||
|
||||
#if ASSETS_LOADING_EXTRA_VERIFICATION
|
||||
|
||||
// Check calculated position of first asset header
|
||||
if (dataCount > 0 && stream->GetPosition() != entries[0].Address)
|
||||
if (assets.Length() > 0 && stream->GetPosition() != entries[0].Address)
|
||||
{
|
||||
LOG(Warning, "Error while asset header location computation.");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Write asset headers
|
||||
for (int32 i = 0; i < dataCount; i++)
|
||||
for (int32 i = 0; i < assets.Length(); i++)
|
||||
{
|
||||
auto& header = data[i];
|
||||
const AssetInitData& header = assets[i];
|
||||
|
||||
// ID
|
||||
stream->Write(header.Header.ID);
|
||||
@@ -974,9 +1052,10 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
stream->WriteUint32(header.SerializedVersion);
|
||||
|
||||
// Chunks mapping
|
||||
for (FlaxChunk* chunk : header.Header.Chunks)
|
||||
for (const FlaxChunk* chunk : header.Header.Chunks)
|
||||
{
|
||||
const int32 index = chunks.Find(chunk);
|
||||
ASSERT_LOW_LAYER(index >= -1 && index <= ALL_ASSET_CHUNKS);
|
||||
stream->WriteInt32(index);
|
||||
}
|
||||
|
||||
@@ -998,14 +1077,12 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
}
|
||||
|
||||
#if ASSETS_LOADING_EXTRA_VERIFICATION
|
||||
|
||||
// Check calculated position of first asset chunk
|
||||
if (chunksCount > 0 && stream->GetPosition() != chunks[0]->LocationInFile.Address)
|
||||
{
|
||||
LOG(Warning, "Error while asset data chunk location computation.");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Write chunks data
|
||||
@@ -1033,7 +1110,7 @@ bool FlaxStorage::Create(WriteStream* stream, const AssetInitData* data, int32 d
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FlaxStorage::Save(AssetInitData& data, bool silentMode)
|
||||
bool FlaxStorage::Save(const AssetInitData& data, bool silentMode)
|
||||
{
|
||||
// Check if can modify the storage
|
||||
if (!AllowDataModifications())
|
||||
@@ -1475,12 +1552,21 @@ int32 FlaxFile::GetEntriesCount() const
|
||||
return _asset.ID.IsValid() ? 1 : 0;
|
||||
}
|
||||
|
||||
void FlaxFile::GetEntry(int32 index, Entry& output) const
|
||||
void FlaxFile::GetEntry(int32 index, Entry& value) const
|
||||
{
|
||||
ASSERT(index == 0);
|
||||
output = _asset;
|
||||
value = _asset;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void FlaxFile::SetEntry(int32 index, const Entry& value)
|
||||
{
|
||||
ASSERT(index == 0);
|
||||
_asset = value;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FlaxFile::GetEntries(Array<Entry>& output) const
|
||||
{
|
||||
if (_asset.ID.IsValid())
|
||||
@@ -1544,19 +1630,36 @@ int32 FlaxPackage::GetEntriesCount() const
|
||||
return _entries.Count();
|
||||
}
|
||||
|
||||
void FlaxPackage::GetEntry(int32 index, Entry& output) const
|
||||
void FlaxPackage::GetEntry(int32 index, Entry& value) const
|
||||
{
|
||||
ASSERT(index >= 0 && index < _entries.Count());
|
||||
for (auto i = _entries.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (index-- <= 0)
|
||||
{
|
||||
output = i->Value;
|
||||
value = i->Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void FlaxPackage::SetEntry(int32 index, const Entry& value)
|
||||
{
|
||||
ASSERT(index >= 0 && index < _entries.Count());
|
||||
for (auto i = _entries.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
if (index-- <= 0)
|
||||
{
|
||||
i->Value = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void FlaxPackage::GetEntries(Array<Entry>& output) const
|
||||
{
|
||||
_entries.GetValues(output);
|
||||
|
||||
@@ -313,8 +313,17 @@ public:
|
||||
/// 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;
|
||||
/// <param name="value">The result.</param>
|
||||
virtual void GetEntry(int32 index, Entry& value) const = 0;
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Sets the asset entry at given index.
|
||||
/// </summary>
|
||||
/// <param name="index">The asset index.</param>
|
||||
/// <param name="value">The input value.</param>
|
||||
virtual void SetEntry(int32 index, const Entry& value) = 0;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the entries in the storage.
|
||||
@@ -424,62 +433,58 @@ public:
|
||||
|
||||
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);
|
||||
bool Save(const 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="asset">The asset 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)
|
||||
FORCE_INLINE static bool Create(const StringView& path, const AssetInitData& asset, bool silentMode = false, const CustomData* customData = nullptr)
|
||||
{
|
||||
return Create(path, &data, 1, silentMode, customData);
|
||||
return Create(path, ToSpan(&asset, 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="assets">The assets 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)
|
||||
FORCE_INLINE static bool Create(const StringView& path, const Array<AssetInitData>& assets, bool silentMode = false, const CustomData* customData = nullptr)
|
||||
{
|
||||
return Create(path, data.Get(), data.Count(), silentMode, customData);
|
||||
return Create(path, ToSpan(assets), 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="assets">The assets 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>
|
||||
static bool Create(const StringView& path, const AssetInitData* data, int32 dataCount, bool silentMode = false, const CustomData* customData = nullptr);
|
||||
static bool Create(const StringView& path, Span<AssetInitData> assets, 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="assets">The assets data to write.</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);
|
||||
|
||||
static bool Create(WriteStream* stream, Span<AssetInitData> assets, const CustomData* customData = nullptr);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
@@ -488,4 +493,5 @@ protected:
|
||||
virtual void AddEntry(Entry& e) = 0;
|
||||
FileReadStream* OpenFile();
|
||||
virtual bool GetEntry(const Guid& id, Entry& e) = 0;
|
||||
bool ReloadSilent();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user