SerializationManager
Bigfoot / Sonarqube (push) Skipped
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 1m4s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Failing after 18s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 42s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Failing after 17s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m3s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Failing after 20s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m11s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Failing after 22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 41s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Failing after 22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 48s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Failing after 25s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m8s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Failing after 29s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m14s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Failing after 33s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 32s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 26s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 52s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 39s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 48s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 40s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 53s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 45s
Bigfoot / Clang Format Checks (push) Successful in 9s
Bigfoot / Sonarqube (push) Skipped
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 1m4s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Failing after 18s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 42s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Failing after 17s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m3s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Failing after 20s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m11s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Failing after 22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 41s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Failing after 22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 48s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Failing after 25s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m8s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Failing after 29s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m14s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Failing after 33s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 32s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 26s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 52s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 39s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 48s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 40s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 53s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 45s
Bigfoot / Clang Format Checks (push) Successful in 9s
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/*********************************************************************
|
||||
* \file SerializationManager.hpp
|
||||
*
|
||||
* \author Romain BOULLARD
|
||||
* \date August 2026
|
||||
*********************************************************************/
|
||||
#ifndef BIGFOOT_ENGINE_SERIALIZATIONMANAGER_SERIALIZATIONMANAGER_HPP
|
||||
#define BIGFOOT_ENGINE_SERIALIZATIONMANAGER_SERIALIZATIONMANAGER_HPP
|
||||
#include <System/UUID/UUID.hpp>
|
||||
|
||||
#include <EASTL/span.h>
|
||||
|
||||
namespace Bigfoot::SerializationManager
|
||||
{
|
||||
/**
|
||||
* Load an Asset, along with every HardReference it transitively points to
|
||||
* Already loaded assets are skipped. Assets that are part of a cyclic dependency are left unloaded
|
||||
*
|
||||
* \param p_uuid The asset to load
|
||||
*/
|
||||
void LoadAsset(const UUID& p_uuid);
|
||||
|
||||
/**
|
||||
* Load several Assets, along with every HardReference they transitively point to
|
||||
* Already loaded assets are skipped. Assets that are part of a cyclic dependency are left unloaded
|
||||
*
|
||||
* \param p_uuids The assets to load
|
||||
*/
|
||||
void LoadAssets(const eastl::span<const UUID> p_uuids);
|
||||
|
||||
#if defined BIGFOOT_TOOLMODE
|
||||
/**
|
||||
* Save an Asset
|
||||
* Fails without saving if a cyclic dependency is detected starting from this asset
|
||||
*
|
||||
* \param p_uuid The asset to save
|
||||
* \return True if the asset was saved, false otherwise
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool SaveAsset(const UUID& p_uuid);
|
||||
|
||||
/**
|
||||
* Save several Assets
|
||||
* Fails without saving any of them if a cyclic dependency is detected starting from any of them
|
||||
*
|
||||
* \param p_uuids The assets to save
|
||||
* \return True if the assets were saved, false otherwise
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool SaveAssets(const eastl::span<const UUID> p_uuids);
|
||||
#endif
|
||||
} // namespace Bigfoot::SerializationManager
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,279 @@
|
||||
/*********************************************************************
|
||||
* \file SerializationManager.cpp
|
||||
*
|
||||
* \author Romain BOULLARD
|
||||
* \date August 2026
|
||||
*********************************************************************/
|
||||
#include <Engine/SerializationManager/SerializationManager.hpp>
|
||||
|
||||
#include <Engine/Asset/AssetLibrary.hpp>
|
||||
#include <Engine/DataAccessor/DataAccessor.hpp>
|
||||
|
||||
#include <Utils/Algorithm/Graph.hpp>
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
namespace Bigfoot::SerializationManager
|
||||
{
|
||||
/*
|
||||
* Build the transitive HardReference graph reachable from p_roots
|
||||
*
|
||||
* \param p_roots The assets to start the discovery from
|
||||
* \param p_discovered Every discovered asset that isn't already loaded, including p_roots
|
||||
* \return Every asset's HardReferences
|
||||
*/
|
||||
[[nodiscard]]
|
||||
ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> BuildDependencyGraph(
|
||||
const eastl::span<const UUID> p_roots,
|
||||
ankerl::unordered_dense::set<UUID>& p_discovered)
|
||||
{
|
||||
ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph;
|
||||
|
||||
eastl::vector<UUID> toQuery;
|
||||
toQuery.reserve(p_roots.size());
|
||||
for (const UUID& uuid: p_roots)
|
||||
{
|
||||
if (!g_activeAssetLibrary->IsLoaded(uuid) && p_discovered.insert(uuid).second)
|
||||
{
|
||||
toQuery.push_back(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
while (!toQuery.empty())
|
||||
{
|
||||
eastl::vector<eastl::vector<UUID>> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery);
|
||||
|
||||
eastl::vector<UUID> nextToQuery;
|
||||
for (std::size_t i = 0; i < toQuery.size(); ++i)
|
||||
{
|
||||
eastl::vector<UUID>& hardReferences = graph[toQuery[i]];
|
||||
hardReferences = std::move(hardReferencesBatch[i]);
|
||||
|
||||
for (const UUID& hardReference: hardReferences)
|
||||
{
|
||||
if (!g_activeAssetLibrary->IsLoaded(hardReference) && p_discovered.insert(hardReference).second)
|
||||
{
|
||||
nextToQuery.push_back(hardReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toQuery = std::move(nextToQuery);
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
void LoadAssets(const eastl::span<const UUID> p_uuids)
|
||||
{
|
||||
ankerl::unordered_dense::set<UUID> discovered;
|
||||
const ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph = BuildDependencyGraph(p_uuids, discovered);
|
||||
|
||||
// Assets that are part of a cyclic HardReference dependency are left out of waves and simply never loaded.
|
||||
eastl::vector<eastl::vector<UUID>> waves;
|
||||
eastl::vector<UUID> cyclicUuids;
|
||||
std::ignore = LayeredTopologicalSort(graph, discovered, waves, cyclicUuids);
|
||||
BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(),
|
||||
ENGINE_LOGGER,
|
||||
"Cyclic HardReference dependency detected while loading assets, involved UUIDs: {}",
|
||||
cyclicUuids);
|
||||
|
||||
// Waves are loaded one after another since a wave's assets may HardReference the previous wave's; within a
|
||||
// wave, every asset is independent of the others, so this is where loading could later be parallelized.
|
||||
for (const eastl::vector<UUID>& wave: waves)
|
||||
{
|
||||
const eastl::vector<eastl::vector<std::byte>> assetFlatbuffers = g_dataAccessor->GetAssetFlatbuffers(wave);
|
||||
for (const eastl::vector<std::byte>& flatbuffer: assetFlatbuffers)
|
||||
{
|
||||
if (flatbuffer.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef BIGFOOT_NOT_OPTIMIZED
|
||||
flatbuffers::Verifier verifier {std::bit_cast<std::uint8_t*>(flatbuffer.data()), flatbuffer.size()};
|
||||
const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier);
|
||||
CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!");
|
||||
#endif
|
||||
|
||||
const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(flatbuffer.data());
|
||||
|
||||
AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(
|
||||
flatbuffers::UnPack(*asset->header()->type_id()));
|
||||
std::ignore = container->Insert(flatbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
void LoadAsset(const UUID& p_uuid)
|
||||
{
|
||||
ankerl::unordered_dense::set<UUID> discovered;
|
||||
const ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph = BuildDependencyGraph(
|
||||
eastl::span<const UUID> {&p_uuid, 1},
|
||||
discovered);
|
||||
|
||||
// Assets that are part of a cyclic HardReference dependency are left out of waves and simply never loaded.
|
||||
eastl::vector<eastl::vector<UUID>> waves;
|
||||
eastl::vector<UUID> cyclicUuids;
|
||||
std::ignore = LayeredTopologicalSort(graph, discovered, waves, cyclicUuids);
|
||||
BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(),
|
||||
ENGINE_LOGGER,
|
||||
"Cyclic HardReference dependency detected while loading asset {}, involved UUIDs: {}",
|
||||
p_uuid,
|
||||
cyclicUuids);
|
||||
|
||||
// Waves are loaded one after another since a wave's assets may HardReference the previous wave's; within a
|
||||
// wave, every asset is independent of the others, so this is where loading could later be parallelized.
|
||||
for (const eastl::vector<UUID>& wave: waves)
|
||||
{
|
||||
for (const UUID& uuid: wave)
|
||||
{
|
||||
const eastl::vector<std::byte> flatbuffer = g_dataAccessor->GetAssetFlatbuffer(uuid);
|
||||
if (flatbuffer.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef BIGFOOT_NOT_OPTIMIZED
|
||||
flatbuffers::Verifier verifier {std::bit_cast<std::uint8_t*>(flatbuffer.data()), flatbuffer.size()};
|
||||
const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier);
|
||||
CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!");
|
||||
#endif
|
||||
|
||||
const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(flatbuffer.data());
|
||||
|
||||
AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(
|
||||
flatbuffers::UnPack(*asset->header()->type_id()));
|
||||
std::ignore = container->Insert(flatbuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
#if defined BIGFOOT_TOOLMODE
|
||||
/*
|
||||
* Finds the cyclic dependencies that may exist in Assets
|
||||
*
|
||||
* \param p_uuids The assets we want to explore
|
||||
* \param p_hardReferences The HardReferences of p_uuids, matching by index
|
||||
* \return Every UUID caught in a cyclic dependency
|
||||
*/
|
||||
[[nodiscard]]
|
||||
eastl::vector<UUID> FindCyclicDependencies(const eastl::span<const UUID> p_uuids,
|
||||
const eastl::span<const eastl::vector<UUID>> p_hardReferences)
|
||||
{
|
||||
ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph;
|
||||
ankerl::unordered_dense::set<UUID> discovered;
|
||||
|
||||
eastl::vector<UUID> toQuery;
|
||||
for (std::size_t i = 0; i < p_uuids.size(); ++i)
|
||||
{
|
||||
discovered.insert(p_uuids[i]);
|
||||
graph[p_uuids[i]] = p_hardReferences[i];
|
||||
}
|
||||
for (const eastl::vector<UUID>& hardReferences: p_hardReferences)
|
||||
{
|
||||
for (const UUID& hardReference: hardReferences)
|
||||
{
|
||||
if (discovered.insert(hardReference).second)
|
||||
{
|
||||
toQuery.push_back(hardReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!toQuery.empty())
|
||||
{
|
||||
eastl::vector<eastl::vector<UUID>> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery);
|
||||
|
||||
eastl::vector<UUID> nextToQuery;
|
||||
for (std::size_t i = 0; i < toQuery.size(); ++i)
|
||||
{
|
||||
eastl::vector<UUID>& hardReferences = graph[toQuery[i]];
|
||||
hardReferences = std::move(hardReferencesBatch[i]);
|
||||
|
||||
for (const UUID& hardReference: hardReferences)
|
||||
{
|
||||
if (discovered.insert(hardReference).second)
|
||||
{
|
||||
nextToQuery.push_back(hardReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toQuery = std::move(nextToQuery);
|
||||
}
|
||||
|
||||
eastl::vector<eastl::vector<UUID>> waves;
|
||||
eastl::vector<UUID> cyclicUuids;
|
||||
std::ignore = LayeredTopologicalSort(graph, discovered, waves, cyclicUuids);
|
||||
return cyclicUuids;
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
bool SaveAsset(const UUID& p_uuid)
|
||||
{
|
||||
AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(p_uuid);
|
||||
const AssetBase* asset = container->AcquireContainerHandle(p_uuid)->GetAsset();
|
||||
|
||||
const eastl::vector<std::byte> flatbuffer = asset->Pack();
|
||||
const eastl::vector<UUID> hardReferences = asset->GetHardReferences(flatbuffer);
|
||||
|
||||
const eastl::vector<UUID> cyclicUuids = FindCyclicDependencies(
|
||||
eastl::span<const UUID> {&p_uuid, 1},
|
||||
eastl::span<const eastl::vector<UUID>> {&hardReferences, 1});
|
||||
BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(),
|
||||
ENGINE_LOGGER,
|
||||
"Cyclic HardReference dependency detected while saving asset {}, involved UUIDs: {}",
|
||||
p_uuid,
|
||||
cyclicUuids);
|
||||
if (!cyclicUuids.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_dataAccessor->SaveAssetFlatbuffer(p_uuid, flatbuffer, hardReferences);
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
bool SaveAssets(const eastl::span<const UUID> p_uuids)
|
||||
{
|
||||
eastl::vector<eastl::vector<std::byte>> flatbuffers;
|
||||
eastl::vector<eastl::vector<UUID>> hardReferences;
|
||||
|
||||
flatbuffers.reserve(p_uuids.size());
|
||||
hardReferences.reserve(p_uuids.size());
|
||||
for (const UUID& uuid: p_uuids)
|
||||
{
|
||||
AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(uuid);
|
||||
const AssetBase* asset = container->AcquireContainerHandle(uuid)->GetAsset();
|
||||
|
||||
const eastl::vector<std::byte> flatbuffer = asset->Pack();
|
||||
|
||||
flatbuffers.push_back(flatbuffer);
|
||||
hardReferences.push_back(asset->GetHardReferences(flatbuffer));
|
||||
}
|
||||
|
||||
const eastl::vector<UUID> cyclicUuids = FindCyclicDependencies(p_uuids, hardReferences);
|
||||
BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(),
|
||||
ENGINE_LOGGER,
|
||||
"Cyclic HardReference dependency detected while saving assets, involved UUIDs: {}",
|
||||
cyclicUuids);
|
||||
if (!cyclicUuids.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
g_dataAccessor->SaveAssetFlatbuffers(p_uuids, flatbuffers, hardReferences);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
} // namespace Bigfoot::SerializationManager
|
||||
@@ -0,0 +1,398 @@
|
||||
/*********************************************************************
|
||||
* \file SerializationManager.cpp
|
||||
*
|
||||
* \author Romain BOULLARD
|
||||
* \date August 2026
|
||||
*********************************************************************/
|
||||
#include <Engine/SerializationManager/SerializationManager.hpp>
|
||||
|
||||
#include <Engine/Asset/AssetLibrary.hpp>
|
||||
#include <Engine/DataAccessor/DataAccessor.hpp>
|
||||
|
||||
#if defined BIGFOOT_TOOLMODE
|
||||
#include <Engine/BigFile/BigFile.hpp>
|
||||
|
||||
#include <EngineTests/BigFileInfo_generated.hpp>
|
||||
#elif defined BIGFOOT_GAME
|
||||
#include <Engine/Sasquatch/Sasquatch.hpp>
|
||||
|
||||
#include <EngineTests/SasquatchInfo_generated.hpp>
|
||||
#endif
|
||||
|
||||
#include <System/File/File.hpp>
|
||||
|
||||
#include <AssetsForTesting/Asset/AssetA.hpp>
|
||||
#include <AssetsForTesting/Asset/AssetB.hpp>
|
||||
#include <AssetsForTesting/Asset/AssetC.hpp>
|
||||
|
||||
#include <EASTL/array.h>
|
||||
#include <EASTL/optional.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Bigfoot
|
||||
{
|
||||
class SerializationManagerFixture: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
m_previousDataAccessor = g_dataAccessor;
|
||||
|
||||
SetActiveAssetLibrary(&m_library);
|
||||
std::ignore = m_library.Insert(&m_containerA);
|
||||
std::ignore = m_library.Insert(&m_containerB);
|
||||
std::ignore = m_library.Insert(&m_containerC);
|
||||
|
||||
// leaf
|
||||
m_leaf.SetName("leaf");
|
||||
m_leafUUID = m_leaf.GetHeader().uuid;
|
||||
|
||||
// middle --> leaf
|
||||
m_middle.SetName("middle");
|
||||
m_middle.GetAsset().ref_a = HardReference<AssetA> {m_leafUUID};
|
||||
m_middleUUID = m_middle.GetHeader().uuid;
|
||||
|
||||
// root
|
||||
// / \
|
||||
// v v
|
||||
// middle --> leaf
|
||||
//
|
||||
// root HardReferences both middle and leaf directly, so leaf ends up shared by two different paths
|
||||
m_root.GetAsset().asset_b_ref = HardReference<AssetB> {m_middleUUID};
|
||||
m_root.GetAsset().inner_table->asset_a_ref = HardReference<AssetA> {m_leafUUID};
|
||||
m_rootUUID = m_root.GetHeader().uuid;
|
||||
|
||||
// cycleX <--> cycleY
|
||||
//
|
||||
// ref_a is typed as a HardReference<AssetA>, but a HardReference only carries a UUID: reusing it to point
|
||||
// at another AssetB is enough to form a cycle for this test
|
||||
m_cycleX.GetAsset().ref_a = HardReference<AssetA> {m_cycleY.GetHeader().uuid};
|
||||
m_cycleY.GetAsset().ref_a = HardReference<AssetA> {m_cycleX.GetHeader().uuid};
|
||||
m_cycleXUUID = m_cycleX.GetHeader().uuid;
|
||||
m_cycleYUUID = m_cycleY.GetHeader().uuid;
|
||||
|
||||
// sharedChild
|
||||
m_sharedChild.SetName("shared");
|
||||
m_sharedChildUUID = m_sharedChild.GetHeader().uuid;
|
||||
|
||||
// multiRootA multiRootB
|
||||
// \ /
|
||||
// v v
|
||||
// sharedChild
|
||||
m_multiRootA.SetName("rootA");
|
||||
m_multiRootA.GetAsset().ref_a = HardReference<AssetA> {m_sharedChildUUID};
|
||||
m_multiRootAUUID = m_multiRootA.GetHeader().uuid;
|
||||
|
||||
m_multiRootB.SetName("rootB");
|
||||
m_multiRootB.GetAsset().ref_a = HardReference<AssetA> {m_sharedChildUUID};
|
||||
m_multiRootBUUID = m_multiRootB.GetHeader().uuid;
|
||||
|
||||
Populate({&m_leaf, &m_middle, &m_root, &m_cycleX, &m_cycleY, &m_sharedChild, &m_multiRootA, &m_multiRootB});
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
SetDataAccessor(m_previousDataAccessor);
|
||||
SetActiveAssetLibrary(&g_assetLibrary);
|
||||
}
|
||||
|
||||
/*
|
||||
* Packs and writes p_assets to the real, on-disk backing store used by the current build (a BigFile in
|
||||
* ToolMode, a baked Sasquatch file in Game), then makes it the active DataAccessor. This lets LoadAsset and
|
||||
* LoadAssets be exercised against a real DataAccessor instead of a mocked one.
|
||||
*
|
||||
* \param p_assets The assets to write, in insertion order
|
||||
*/
|
||||
void Populate(const eastl::vector<AssetBase*>& p_assets)
|
||||
{
|
||||
#if defined BIGFOOT_TOOLMODE
|
||||
m_bigFile.emplace(File {BIGFILE_ENGINETESTS_LOCATION});
|
||||
m_bigFile->Clear();
|
||||
|
||||
for (AssetBase* const asset: p_assets)
|
||||
{
|
||||
const eastl::vector<std::byte> flatbuffer = asset->Pack();
|
||||
m_bigFile->SaveAssetFlatbuffer(asset->GetHeader().uuid, flatbuffer, asset->GetHardReferences(flatbuffer));
|
||||
}
|
||||
|
||||
SetDataAccessor(&*m_bigFile);
|
||||
#elif defined BIGFOOT_GAME
|
||||
struct SasquatchData
|
||||
{
|
||||
UUID m_uuid;
|
||||
eastl::vector<std::byte> m_flatbuffer;
|
||||
eastl::vector<UUID> m_hardReferences;
|
||||
};
|
||||
|
||||
eastl::vector<SasquatchData> datas;
|
||||
datas.reserve(p_assets.size());
|
||||
|
||||
std::size_t totalFlatbufferBytes = 0;
|
||||
std::size_t totalHardReferencesBytes = 0;
|
||||
for (AssetBase* const asset: p_assets)
|
||||
{
|
||||
eastl::vector<std::byte> flatbuffer = asset->Pack();
|
||||
eastl::vector<UUID> hardReferences = asset->GetHardReferences(flatbuffer);
|
||||
|
||||
totalFlatbufferBytes += flatbuffer.size();
|
||||
totalHardReferencesBytes += hardReferences.size() * UUID::UUID_BYTE_SIZE;
|
||||
|
||||
datas.push_back(SasquatchData {.m_uuid = asset->GetHeader().uuid,
|
||||
.m_flatbuffer = std::move(flatbuffer),
|
||||
.m_hardReferences = std::move(hardReferences)});
|
||||
}
|
||||
|
||||
File {SASQUATCH_ENGINETESTS_LOCATION}.Delete();
|
||||
{
|
||||
SasquatchWriter writer {File {SASQUATCH_ENGINETESTS_LOCATION},
|
||||
datas.size(),
|
||||
totalFlatbufferBytes,
|
||||
totalHardReferencesBytes};
|
||||
for (const SasquatchData& data: datas)
|
||||
{
|
||||
writer.WriteAsset(data.m_uuid, data.m_flatbuffer, data.m_hardReferences);
|
||||
}
|
||||
}
|
||||
|
||||
m_sasquatch.emplace(File {SASQUATCH_ENGINETESTS_LOCATION});
|
||||
SetDataAccessor(&*m_sasquatch);
|
||||
#endif
|
||||
}
|
||||
|
||||
DataAccessor* m_previousDataAccessor = nullptr;
|
||||
|
||||
AssetLibrary m_library;
|
||||
AssetContainer<AssetA> m_containerA;
|
||||
AssetContainer<AssetB> m_containerB;
|
||||
AssetContainer<AssetC> m_containerC;
|
||||
|
||||
// An asset with no HardReferences of its own
|
||||
AssetA m_leaf;
|
||||
UUID m_leafUUID;
|
||||
|
||||
// HardReferences m_leaf
|
||||
AssetB m_middle;
|
||||
UUID m_middleUUID;
|
||||
|
||||
// HardReferences both m_middle and m_leaf directly
|
||||
AssetC m_root;
|
||||
UUID m_rootUUID;
|
||||
|
||||
// A pair of assets HardReferencing each other
|
||||
AssetB m_cycleX;
|
||||
AssetB m_cycleY;
|
||||
UUID m_cycleXUUID;
|
||||
UUID m_cycleYUUID;
|
||||
|
||||
// HardReferenced by both m_multiRootA and m_multiRootB
|
||||
AssetA m_sharedChild;
|
||||
UUID m_sharedChildUUID;
|
||||
|
||||
AssetB m_multiRootA;
|
||||
UUID m_multiRootAUUID;
|
||||
AssetB m_multiRootB;
|
||||
UUID m_multiRootBUUID;
|
||||
|
||||
#if defined BIGFOOT_TOOLMODE
|
||||
eastl::optional<BigFile> m_bigFile;
|
||||
#elif defined BIGFOOT_GAME
|
||||
eastl::optional<Sasquatch> m_sasquatch;
|
||||
#endif
|
||||
};
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, LoadAsset_ShouldLoadAnAssetWithNoHardReferences)
|
||||
{
|
||||
const HardReference<AssetA> leafRef {m_leafUUID};
|
||||
ASSERT_EQ(leafRef.Get(), nullptr);
|
||||
|
||||
SerializationManager::LoadAsset(m_leafUUID);
|
||||
|
||||
ASSERT_NE(leafRef.Get(), nullptr);
|
||||
EXPECT_EQ(leafRef.Get()->Pack(), m_leaf.Pack());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, LoadAsset_ShouldTransitivelyLoadEveryHardReferencedAsset)
|
||||
{
|
||||
const HardReference<AssetA> leafRef {m_leafUUID};
|
||||
const HardReference<AssetB> middleRef {m_middleUUID};
|
||||
const HardReference<AssetC> rootRef {m_rootUUID};
|
||||
|
||||
ASSERT_EQ(leafRef.Get(), nullptr);
|
||||
ASSERT_EQ(middleRef.Get(), nullptr);
|
||||
ASSERT_EQ(rootRef.Get(), nullptr);
|
||||
|
||||
SerializationManager::LoadAsset(m_rootUUID);
|
||||
|
||||
ASSERT_NE(leafRef.Get(), nullptr);
|
||||
ASSERT_NE(middleRef.Get(), nullptr);
|
||||
ASSERT_NE(rootRef.Get(), nullptr);
|
||||
|
||||
EXPECT_EQ(leafRef.Get()->Pack(), m_leaf.Pack());
|
||||
EXPECT_EQ(middleRef.Get()->Pack(), m_middle.Pack());
|
||||
EXPECT_EQ(rootRef.Get()->Pack(), m_root.Pack());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, LoadAsset_ShouldSkipAssetsThatAreCaughtInACycle)
|
||||
{
|
||||
const HardReference<AssetB> xRef {m_cycleXUUID};
|
||||
const HardReference<AssetB> yRef {m_cycleYUUID};
|
||||
|
||||
ASSERT_EQ(xRef.Get(), nullptr);
|
||||
ASSERT_EQ(yRef.Get(), nullptr);
|
||||
|
||||
SerializationManager::LoadAsset(m_cycleXUUID);
|
||||
|
||||
EXPECT_EQ(xRef.Get(), nullptr);
|
||||
EXPECT_EQ(yRef.Get(), nullptr);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, LoadAsset_ShouldNotReloadAnAssetThatIsAlreadyLoaded)
|
||||
{
|
||||
AssetA* alreadyLoaded = static_cast<AssetA*>(m_containerA.Insert()->GetAsset());
|
||||
alreadyLoaded->SetName("in-memory");
|
||||
const UUID uuid = alreadyLoaded->GetHeader().uuid;
|
||||
|
||||
const HardReference<AssetA> ref {uuid};
|
||||
ASSERT_NE(ref.Get(), nullptr);
|
||||
|
||||
SerializationManager::LoadAsset(uuid);
|
||||
|
||||
ASSERT_NE(ref.Get(), nullptr);
|
||||
EXPECT_STREQ(ref.Get()->GetHeader().name.c_str(), "in-memory");
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, LoadAssets_ShouldLoadEveryRootAndMergeSharedHardReferences)
|
||||
{
|
||||
const HardReference<AssetA> sharedChildRef {m_sharedChildUUID};
|
||||
const HardReference<AssetB> rootARef {m_multiRootAUUID};
|
||||
const HardReference<AssetB> rootBRef {m_multiRootBUUID};
|
||||
|
||||
ASSERT_EQ(sharedChildRef.Get(), nullptr);
|
||||
ASSERT_EQ(rootARef.Get(), nullptr);
|
||||
ASSERT_EQ(rootBRef.Get(), nullptr);
|
||||
|
||||
const eastl::array<UUID, 2> roots {m_multiRootAUUID, m_multiRootBUUID};
|
||||
SerializationManager::LoadAssets(roots);
|
||||
|
||||
ASSERT_NE(sharedChildRef.Get(), nullptr);
|
||||
ASSERT_NE(rootARef.Get(), nullptr);
|
||||
ASSERT_NE(rootBRef.Get(), nullptr);
|
||||
|
||||
EXPECT_EQ(sharedChildRef.Get()->Pack(), m_sharedChild.Pack());
|
||||
EXPECT_EQ(rootARef.Get()->Pack(), m_multiRootA.Pack());
|
||||
EXPECT_EQ(rootBRef.Get()->Pack(), m_multiRootB.Pack());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
#ifdef BIGFOOT_TOOLMODE
|
||||
TEST_F(SerializationManagerFixture, SaveAsset_ShouldSaveTheAsset)
|
||||
{
|
||||
AssetB* asset = static_cast<AssetB*>(m_containerB.Insert()->GetAsset());
|
||||
const UUID referencedUUID;
|
||||
asset->GetAsset().ref_a = HardReference<AssetA> {referencedUUID};
|
||||
|
||||
const UUID uuid = asset->GetHeader().uuid;
|
||||
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuid));
|
||||
|
||||
EXPECT_TRUE(SerializationManager::SaveAsset(uuid));
|
||||
|
||||
ASSERT_TRUE(g_dataAccessor->Has(uuid));
|
||||
EXPECT_THAT(g_dataAccessor->GetAssetHardReferences(uuid), ::testing::ElementsAre(referencedUUID));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, SaveAsset_ShouldFailAndNotSaveIfAnAssetHardReferencesItself)
|
||||
{
|
||||
AssetB* asset = static_cast<AssetB*>(m_containerB.Insert()->GetAsset());
|
||||
const UUID uuid = asset->GetHeader().uuid;
|
||||
asset->GetAsset().ref_a = HardReference<AssetA> {uuid};
|
||||
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuid));
|
||||
|
||||
EXPECT_FALSE(SerializationManager::SaveAsset(uuid));
|
||||
|
||||
EXPECT_FALSE(g_dataAccessor->Has(uuid));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, SaveAssets_ShouldFailAndNotSaveIfTwoAssetsInTheSameSaveHardReferenceEachOther)
|
||||
{
|
||||
AssetB* assetX = static_cast<AssetB*>(m_containerB.Insert()->GetAsset());
|
||||
AssetB* assetY = static_cast<AssetB*>(m_containerB.Insert()->GetAsset());
|
||||
|
||||
const UUID uuidX = assetX->GetHeader().uuid;
|
||||
const UUID uuidY = assetY->GetHeader().uuid;
|
||||
|
||||
assetX->GetAsset().ref_a = HardReference<AssetA> {uuidY};
|
||||
assetY->GetAsset().ref_a = HardReference<AssetA> {uuidX};
|
||||
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuidX));
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuidY));
|
||||
|
||||
const eastl::array<UUID, 2> uuids {uuidX, uuidY};
|
||||
EXPECT_FALSE(SerializationManager::SaveAssets(uuids));
|
||||
|
||||
EXPECT_FALSE(g_dataAccessor->Has(uuidX));
|
||||
EXPECT_FALSE(g_dataAccessor->Has(uuidY));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, SaveAssets_ShouldFailAndNotSaveIfAnAlreadySavedHardReferencePointsBackToIt)
|
||||
{
|
||||
AssetB* asset = static_cast<AssetB*>(m_containerB.Insert()->GetAsset());
|
||||
const UUID uuid = asset->GetHeader().uuid;
|
||||
const UUID referencedUUID;
|
||||
asset->GetAsset().ref_a = HardReference<AssetA> {referencedUUID};
|
||||
|
||||
// referencedUUID is not part of this save; its own already-saved HardReferences (read back from the BigFile)
|
||||
// point back to uuid, closing a cycle through previously persisted data. Its flatbuffer content is irrelevant.
|
||||
const AssetA placeholder;
|
||||
const eastl::array<UUID, 1> referencedHardReferences {uuid};
|
||||
g_dataAccessor->SaveAssetFlatbuffer(referencedUUID, placeholder.Pack(), referencedHardReferences);
|
||||
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuid));
|
||||
|
||||
EXPECT_FALSE(SerializationManager::SaveAsset(uuid));
|
||||
|
||||
EXPECT_FALSE(g_dataAccessor->Has(uuid));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SerializationManagerFixture, SaveAssets_ShouldSaveEveryAsset)
|
||||
{
|
||||
const AssetA* assetX = static_cast<const AssetA*>(m_containerA.Insert()->GetAsset());
|
||||
const AssetA* assetY = static_cast<const AssetA*>(m_containerA.Insert()->GetAsset());
|
||||
|
||||
const UUID uuidX = assetX->GetHeader().uuid;
|
||||
const UUID uuidY = assetY->GetHeader().uuid;
|
||||
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuidX));
|
||||
ASSERT_FALSE(g_dataAccessor->Has(uuidY));
|
||||
|
||||
const eastl::array<UUID, 2> uuids {uuidX, uuidY};
|
||||
EXPECT_TRUE(SerializationManager::SaveAssets(uuids));
|
||||
|
||||
ASSERT_TRUE(g_dataAccessor->Has(uuidX));
|
||||
ASSERT_TRUE(g_dataAccessor->Has(uuidY));
|
||||
EXPECT_EQ(g_dataAccessor->GetAssetFlatbuffer(uuidX), assetX->Pack());
|
||||
EXPECT_EQ(g_dataAccessor->GetAssetFlatbuffer(uuidY), assetY->Pack());
|
||||
}
|
||||
#endif
|
||||
} // namespace Bigfoot
|
||||
Reference in New Issue
Block a user