From 1b32cd07f6fcf59e01345394d131c8dd96d3876b Mon Sep 17 00:00:00 2001 From: Romain BOULLARD Date: Sat, 8 Aug 2026 01:28:03 +0200 Subject: [PATCH] AssetContainer tests --- .../Include/Engine/Asset/AssetContainer.hpp | 218 +++++++++++++++--- .../Include/Engine/Asset/AssetLibrary.hpp | 4 +- .../Engine/Include/Engine/Asset/Reference.hpp | 4 +- Bigfoot/Tests/Engine/Asset/AssetContainer.cpp | 87 +++++++ 4 files changed, 278 insertions(+), 35 deletions(-) diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp index b17d756..1a4d5e8 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp @@ -27,38 +27,69 @@ class ContainerHandle ~ContainerHandle() = default; + /* + * Increment the HardRef count + * + */ void IncrementHardRefCount() { ++m_hardRefCount; } + /* + * Decrement the HardRef count + * + */ void DecrementHardRefCount() { --m_hardRefCount; } + /* + * Increment the SoftRef count + * + */ void IncrementSoftRefCount() { ++m_softRefCount; } + /* + * Decrement the SoftRef count + * + */ void DecrementSoftRefCount() { --m_softRefCount; } + /* + * Get the Asset + * + * \return The asset, can be nullptr + */ [[nodiscard]] AssetBase* GetAsset() { return m_asset; } + /* + * Get the Asset + * + * \return The asset + */ [[nodiscard]] const AssetBase* GetAsset() const { return m_asset; } + /* + * Set the asset + * + * \param p_asset The asset + */ void SetAsset(AssetBase* p_asset) { m_asset = p_asset; @@ -68,8 +99,19 @@ class ContainerHandle ContainerHandle& operator=(ContainerHandle&& p_handle) = default; private: + /* + * The HardRef count + */ std::uint32_t m_hardRefCount = 0; + + /* + * The SoftRef count + */ std::uint32_t m_softRefCount = 0; + + /* + * The asset + */ AssetBase* m_asset = nullptr; }; @@ -80,51 +122,135 @@ class AssetContainerBase public: virtual ~AssetContainerBase() = default; - [[nodiscard]] - SlotMapKey GetAssetSlotKey(const UUID& p_uuid) const - { - if (const auto it = m_uuidToAssetSlotKey.find(p_uuid); it != m_uuidToAssetSlotKey.end()) - { - return it->second; - } - - return SlotMapKey {}; - } - + /* + * Check if an Asset is in the AssetContainer or not + * + * \param p_uuid The UUID to check + * \return True if in AssetContainer, false otherwise + */ [[nodiscard]] bool Has(const UUID& p_uuid) const { - return m_uuidToAssetSlotKey.contains(p_uuid); + const Entry* entry = FindEntry(p_uuid); + return entry != nullptr && entry->m_assetSlotKey.Valid(); } + /* + * Acquires a ContainerHandle for a given UUID + * Will create it if it does not exists + * + * \param p_uuid The UUID to get ContainerHandle for + * \return A ContainerHandle for the UUID + */ [[nodiscard]] - ContainerHandle& AcquireHandle(const UUID& p_uuid) + ContainerHandle& AcquireContainerHandle(const UUID& p_uuid) { - if (const auto it = m_uuidToContainerHandleSlotKey.find(p_uuid); it != m_uuidToContainerHandleSlotKey.end()) + return *m_handles.Get(GetOrCreateEntry(p_uuid).m_handleSlotKey); + } + + /* + * Remove an Asset from the AssetContainer + * + * \param p_uuid The UUID to remove + */ + virtual void Remove(const UUID& p_uuid) + { + if (Entry* entry = FindEntry(p_uuid); entry != nullptr && entry->m_handleSlotKey.Valid()) { - return *m_handles.Get(it->second); + m_handles.Remove(entry->m_handleSlotKey); + m_uuidToEntry.erase(p_uuid); } - - const SlotMapKey key = m_handles.Insert(); - m_uuidToContainerHandleSlotKey.emplace(p_uuid, key); - - return *m_handles.Get(key); } AssetContainerBase& operator=(const AssetContainerBase& p_container) = delete; AssetContainerBase& operator=(AssetContainerBase&& p_container) = default; protected: + struct Entry + { + /* + * SlotMapKey to the handle + */ + SlotMapKey m_handleSlotKey; + + /* + * SlotMapKey to the asset + */ + SlotMapKey m_assetSlotKey; + }; + AssetContainerBase() = default; AssetContainerBase(const AssetContainerBase& p_container) = delete; AssetContainerBase(AssetContainerBase&& p_container) = default; - ankerl::unordered_dense::segmented_map m_uuidToAssetSlotKey; - ankerl::unordered_dense::segmented_map m_uuidToContainerHandleSlotKey; + /* + * Finds an entry matching the Asset + * + * \param p_uuid The UUID to find + * \return Pointer to an Entry, nullptr otherwise + */ + [[nodiscard]] + Entry* FindEntry(const UUID& p_uuid) + { + const auto it = m_uuidToEntry.find(p_uuid); + return it != m_uuidToEntry.end() ? &it->second : nullptr; + } + + /* + * Finds an entry matching the Asset + * + * \param p_uuid The UUID to find + * \return Pointer to an Entry, nullptr otherwise + */ + [[nodiscard]] + const Entry* FindEntry(const UUID& p_uuid) const + { + const auto it = m_uuidToEntry.find(p_uuid); + return it != m_uuidToEntry.end() ? &it->second : nullptr; + } + + /* + * Finds an entry matching the Asset + * Creates it if it does not exist + * + * \param p_uuid The UUID to find + * \return The entry matching the Asset + */ + [[nodiscard]] + Entry& GetOrCreateEntry(const UUID& p_uuid) + { + auto [it, inserted] = m_uuidToEntry.try_emplace(p_uuid); + if (inserted) + { + it->second.m_handleSlotKey = m_handles.Insert(); + } + + return it->second; + } + + /* + * Get a ContainerHandle + * + * \param p_handleSlotKey The key to the container + * \return The container + */ + [[nodiscard]] + ContainerHandle& FindContainerHandle(const SlotMapKey p_handleSlotKey) + { + return *m_handles.Get(p_handleSlotKey); + } private: + /* + * HandleContainers + */ StableSlotMap m_handles; + + /* + * Every Asset added to the map + */ + ankerl::unordered_dense::segmented_map m_uuidToEntry; }; /****************************************************************************************/ @@ -140,6 +266,13 @@ class AssetContainer: public AssetContainerBase ~AssetContainer() = default; + /* + * Inserts an Asset to the AssetContainer + * + * \tparam ARGS Arguments to construct the asset + * \param p_args The arguments + * \return The key to the inserted asset. + */ template [[nodiscard]] SlotMapKey Insert(ARGS&&... p_args) @@ -147,46 +280,69 @@ class AssetContainer: public AssetContainerBase SlotMapKey slotKey = m_assets.Insert(std::forward(p_args)...); ASSET* asset = m_assets.Get(slotKey); - if (const auto it = m_uuidToAssetSlotKey.find(asset->GetHeader().uuid); it != m_uuidToAssetSlotKey.end()) + Entry& entry = GetOrCreateEntry(asset->GetHeader().uuid); + if (entry.m_assetSlotKey.Valid()) { m_assets.Remove(slotKey); - slotKey = it->second; + slotKey = entry.m_assetSlotKey; } else { - m_uuidToAssetSlotKey.emplace(asset->GetHeader().uuid, slotKey); - AcquireHandle(asset->GetHeader().uuid).SetAsset(asset); + entry.m_assetSlotKey = slotKey; + FindContainerHandle(entry.m_handleSlotKey).SetAsset(asset); } return slotKey; } + /* + * Get the Asset + * + * \param p_key The key to the asset + * \return Pointer to the Asset + */ [[nodiscard]] ASSET* Get(const SlotMapKey p_key) { return m_assets.Get(p_key); } + /* + * Get the Asset + * + * \param p_key The key to the asset + * \return Pointer to the Asset + */ [[nodiscard]] const ASSET* Get(const SlotMapKey p_key) const { return m_assets.Get(p_key); } - void Remove(const UUID& p_uuid) + /* + * Remove an Asset from the AssetContainer + * + * \param p_uuid The UUID to remove + */ + void Remove(const UUID& p_uuid) override { - if (const auto it = m_uuidToAssetSlotKey.find(p_uuid); it != m_uuidToAssetSlotKey.end()) + if (Entry* entry = FindEntry(p_uuid); entry != nullptr && entry->m_assetSlotKey.Valid()) { - AcquireHandle(p_uuid).SetAsset(nullptr); - m_assets.Remove(it->second); - m_uuidToAssetSlotKey.erase(it->first); + FindContainerHandle(entry->m_handleSlotKey).SetAsset(nullptr); + m_assets.Remove(entry->m_assetSlotKey); + entry->m_assetSlotKey = SlotMapKey {}; } + + AssetContainerBase::Remove(p_uuid); } AssetContainer& operator=(const AssetContainer& p_container) = delete; AssetContainer& operator=(AssetContainer&& p_container) = default; private: + /* + * The assets + */ StableSlotMap m_assets; }; } // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp index f8023c8..49c540c 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp @@ -118,7 +118,7 @@ class AssetLibrary { for (const auto& [typeID, container]: m_assetContainers) { - if (container->GetAssetSlotKey(p_uuid).Valid()) + if (container->Has(p_uuid)) { return container; } @@ -138,7 +138,7 @@ class AssetLibrary { for (const auto& [typeID, container]: m_assetContainers) { - if (container->GetAssetSlotKey(p_uuid).Valid()) + if (container->Has(p_uuid)) { return container; } diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp index cf34ca1..79833f9 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp @@ -116,7 +116,7 @@ class HardReference { if (!m_handle) { - m_handle = &assetContainer->AcquireHandle(m_uuid); + m_handle = &assetContainer->AcquireContainerHandle(m_uuid); } m_handle->IncrementSoftRefCount(); } @@ -234,7 +234,7 @@ class SoftReference { if (!m_handle) { - m_handle = &assetContainer->AcquireHandle(m_uuid); + m_handle = &assetContainer->AcquireContainerHandle(m_uuid); } m_handle->IncrementSoftRefCount(); } diff --git a/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp index a1a51fb..c5f9ff4 100644 --- a/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp +++ b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp @@ -19,4 +19,91 @@ class AssetContainerFixture: public ::testing::Test /****************************************************************************************/ +TEST_F(AssetContainerFixture, Has_ShouldReturnTrueIfTheContainerHasTheAsset) +{ + AssetA asset1; + std::ignore = m_containerA.Insert(asset1); + EXPECT_TRUE(m_containerA.Has(asset1.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Has_ShouldReturnFalseIfTheContainerDoesNotHaveTheAsset) +{ + AssetA asset1; + EXPECT_FALSE(m_containerA.Has(asset1.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, AcquireHandle_ShouldReturnTheAssociatedContainerHandle) +{ + AssetA asset1; + std::ignore = m_containerA.Insert(asset1); + + const ContainerHandle& handle = m_containerA.AcquireContainerHandle(asset1.GetHeader().uuid); + EXPECT_EQ(static_cast(handle.GetAsset())->GetHeader().uuid, asset1.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, AcquireHandle_ShouldCreateTheAssociatedContainerHandleIfItDoesNotExists) +{ + AssetA asset1; + + const ContainerHandle& handle = m_containerA.AcquireContainerHandle(asset1.GetHeader().uuid); + EXPECT_EQ(handle.GetAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Insert_ShouldReturnValidAndUniqueSlotKeyOnInsert) +{ + AssetA asset1; + const SlotMapKey slotKey = m_containerA.Insert(asset1); + EXPECT_TRUE(slotKey.Valid()); + EXPECT_NE(slotKey, m_containerA.Insert()); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Insert_ShouldReturnTheSameSlotKeyInCaseOfDuplicatedUUID) +{ + AssetA asset; + const SlotMapKey slotKey = m_containerA.Insert(asset); + EXPECT_EQ(slotKey, m_containerA.Insert(asset)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Get_ShouldReturnTheAssetWithTheSlotKey) +{ + AssetA asset; + const SlotMapKey slotKey = m_containerA.Insert(asset); + + EXPECT_EQ(m_containerA.Get(slotKey)->GetHeader().uuid, asset.GetHeader().uuid); + EXPECT_EQ(const_cast&>(m_containerA).Get(slotKey)->GetHeader().uuid, + asset.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Get_ShouldReturnNullptrIfSlotKeyNotInContainer) +{ + EXPECT_EQ(m_containerA.Get(SlotMapKey {1, 0}), nullptr); + EXPECT_EQ(const_cast&>(m_containerA).Get(SlotMapKey {1, 0}), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Remove_ShouldRemoveTheAsset) +{ + AssetA asset; + const SlotMapKey slotKey = m_containerA.Insert(asset); + + m_containerA.Remove(asset.GetHeader().uuid); + + EXPECT_EQ(m_containerA.Get(slotKey), nullptr); + EXPECT_FALSE(m_containerA.Has(asset.GetHeader().uuid)); +} } // namespace Bigfoot