/********************************************************************* * \file SerializationManager.cpp * * \author Romain BOULLARD * \date August 2026 *********************************************************************/ #include #include #include #include #include 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> BuildDependencyGraph( const eastl::span p_roots, ankerl::unordered_dense::set& p_discovered) { ankerl::unordered_dense::map> graph; eastl::vector 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> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery); eastl::vector nextToQuery; for (std::size_t i = 0; i < toQuery.size(); ++i) { eastl::vector& 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 p_uuids) { ankerl::unordered_dense::set discovered; const ankerl::unordered_dense::map> 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> waves; eastl::vector 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& wave: waves) { const eastl::vector> assetFlatbuffers = g_dataAccessor->GetAssetFlatbuffers(wave); for (const eastl::vector& flatbuffer: assetFlatbuffers) { if (flatbuffer.empty()) { continue; } #ifdef BIGFOOT_NOT_OPTIMIZED flatbuffers::Verifier verifier {std::bit_cast(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 discovered; const ankerl::unordered_dense::map> graph = BuildDependencyGraph( eastl::span {&p_uuid, 1}, discovered); // Assets that are part of a cyclic HardReference dependency are left out of waves and simply never loaded. eastl::vector> waves; eastl::vector 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& wave: waves) { for (const UUID& uuid: wave) { const eastl::vector flatbuffer = g_dataAccessor->GetAssetFlatbuffer(uuid); if (flatbuffer.empty()) { continue; } #ifdef BIGFOOT_NOT_OPTIMIZED flatbuffers::Verifier verifier {std::bit_cast(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 FindCyclicDependencies(const eastl::span p_uuids, const eastl::span> p_hardReferences) { ankerl::unordered_dense::map> graph; ankerl::unordered_dense::set discovered; eastl::vector 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& hardReferences: p_hardReferences) { for (const UUID& hardReference: hardReferences) { if (discovered.insert(hardReference).second) { toQuery.push_back(hardReference); } } } while (!toQuery.empty()) { eastl::vector> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery); eastl::vector nextToQuery; for (std::size_t i = 0; i < toQuery.size(); ++i) { eastl::vector& 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> waves; eastl::vector 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 flatbuffer = asset->Pack(); const eastl::vector hardReferences = asset->GetHardReferences(flatbuffer); const eastl::vector cyclicUuids = FindCyclicDependencies( eastl::span {&p_uuid, 1}, eastl::span> {&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 p_uuids) { eastl::vector> flatbuffers; eastl::vector> 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 flatbuffer = asset->Pack(); flatbuffers.push_back(flatbuffer); hardReferences.push_back(asset->GetHardReferences(flatbuffer)); } const eastl::vector 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