BigFile/Sasquatch #1

Merged
rboullard merged 100 commits from BigFile into Development 2026-09-01 16:32:02 +00:00
3 changed files with 731 additions and 0 deletions
Showing only changes of commit 234c104700 - Show all commits
@@ -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