diff --git a/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp b/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp new file mode 100644 index 0000000..0f160e6 --- /dev/null +++ b/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp @@ -0,0 +1,188 @@ +/********************************************************************* + * \file AssetContainer.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +namespace Bigfoot +{ +ContainerHandle::ContainerHandle(AssetContainerBase* p_assetContainer, const UUID p_uuid): + m_assetContainer(p_assetContainer), + m_uuid(p_uuid) +{ +} + +/****************************************************************************************/ + +std::uint32_t ContainerHandle::GetHardRefCount() const +{ + return m_hardRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::IncrementHardRefCount() +{ + if (m_hardRefCount == 0) + { + m_assetContainer->CancelRemoval(m_uuid); + } + ++m_hardRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::DecrementHardRefCount() +{ + --m_hardRefCount; + if (m_hardRefCount == 0) + { + m_assetContainer->QueueForRemoval(m_uuid); + } +} + +/****************************************************************************************/ + +std::uint32_t ContainerHandle::GetSoftRefCount() const +{ + return m_softRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::IncrementSoftRefCount() +{ + ++m_softRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::DecrementSoftRefCount() +{ + --m_softRefCount; +} + +/****************************************************************************************/ + +AssetBase* ContainerHandle::GetAsset() +{ + return m_asset; +} + +/****************************************************************************************/ + +const AssetBase* ContainerHandle::GetAsset() const +{ + return m_asset; +} + +/****************************************************************************************/ + +void ContainerHandle::SetAsset(AssetBase* p_asset) +{ + m_asset = p_asset; +} + +/****************************************************************************************/ + +bool AssetContainerBase::Has(const UUID& p_uuid) const +{ + const Entry* entry = FindEntry(p_uuid); + return entry != nullptr && entry->m_assetSlotKey.Valid(); +} + +/****************************************************************************************/ + +ContainerHandle* AssetContainerBase::AcquireContainerHandle(const UUID& p_uuid) +{ + return GetOrCreateEntry(p_uuid).m_handle; +} + +/****************************************************************************************/ + +void AssetContainerBase::Remove(const UUID& p_uuid) +{ + if (Entry* entry = FindEntry(p_uuid); entry != nullptr) + { + if (entry->m_assetSlotKey.Valid()) + { + entry->m_handle->SetAsset(nullptr); + RemoveAsset(entry->m_assetSlotKey); + entry->m_assetSlotKey = SlotMapKey {}; + } + m_handles.Remove(entry->m_handleSlotKey); + m_uuidToEntry.erase(p_uuid); + } +} + +/****************************************************************************************/ + +void AssetContainerBase::QueueForRemoval(const UUID& p_uuid) +{ + m_pendingRemovalUuids.push_back(p_uuid); +} + +/****************************************************************************************/ + +void AssetContainerBase::CancelRemoval(const UUID& p_uuid) +{ + const auto it = eastl::find(m_pendingRemovalUuids.begin(), m_pendingRemovalUuids.end(), p_uuid); + if (it != m_pendingRemovalUuids.end()) + { + m_pendingRemovalUuids.erase_unsorted(it); + } +} + +/****************************************************************************************/ + +void AssetContainerBase::ProcessPendingRemovals() +{ + eastl::vector toRemove; + toRemove.swap(m_pendingRemovalUuids); + + for (const UUID& uuid: toRemove) + { + Remove(uuid); + } +} + +/****************************************************************************************/ + +bool AssetContainerBase::IsMarkedForRemove(const UUID& p_uuid) +{ + return eastl::find(m_pendingRemovalUuids.begin(), m_pendingRemovalUuids.end(), p_uuid) != + m_pendingRemovalUuids.end(); +} + +/****************************************************************************************/ + +AssetContainerBase::Entry* AssetContainerBase::FindEntry(const UUID& p_uuid) +{ + const auto it = m_uuidToEntry.find(p_uuid); + return it != m_uuidToEntry.end() ? &it->second : nullptr; +} + +/****************************************************************************************/ + +const AssetContainerBase::Entry* AssetContainerBase::FindEntry(const UUID& p_uuid) const +{ + const auto it = m_uuidToEntry.find(p_uuid); + return it != m_uuidToEntry.end() ? &it->second : nullptr; +} + +/****************************************************************************************/ + +AssetContainerBase::Entry& AssetContainerBase::GetOrCreateEntry(const UUID& p_uuid) +{ + auto [it, inserted] = m_uuidToEntry.try_emplace(p_uuid); + if (inserted) + { + it->second.m_handleSlotKey = m_handles.Insert(this, p_uuid); + it->second.m_handle = m_handles.Get(it->second.m_handleSlotKey); + } + + return it->second; +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp b/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp index 72ea5b0..0ea4f15 100644 --- a/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp +++ b/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp @@ -69,6 +69,16 @@ bool AssetLibrary::IsLoaded(const UUID& p_uuid) const /****************************************************************************************/ +void AssetLibrary::ProcessPendingRemovals() { + + for (const auto [typeId, container] : m_assetContainers) + { + container->ProcessPendingRemovals(); + } +} + +/****************************************************************************************/ + void SetActiveAssetLibrary(AssetLibrary* p_assetLibrary) { g_activeAssetLibrary = p_assetLibrary; diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp index 1e4d591..aee91a1 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp @@ -18,40 +18,41 @@ namespace Bigfoot { +class AssetContainerBase; + /** * An indirection that allows References to immediately get the asset, without fetching the actual container */ class ContainerHandle { public: + /** + * Constructor + * + * \param p_assetContainer The originating assetcontainer + * \param p_uuid The UUID of tha asset + */ + ContainerHandle(AssetContainerBase* p_assetContainer, const UUID p_uuid); + /* * Get the HardRef count * * \return The HardRef count. */ [[nodiscard]] - std::uint32_t GetHardRefCount() const - { - return m_hardRefCount; - } + std::uint32_t GetHardRefCount() const; /* * Increment the HardRef count * */ - void IncrementHardRefCount() - { - ++m_hardRefCount; - } + void IncrementHardRefCount(); /* * Decrement the HardRef count * */ - void DecrementHardRefCount() - { - --m_hardRefCount; - } + void DecrementHardRefCount(); /* * Get the SoftRef count @@ -59,28 +60,19 @@ class ContainerHandle * \return The SoftRef count. */ [[nodiscard]] - std::uint32_t GetSoftRefCount() const - { - return m_softRefCount; - } + std::uint32_t GetSoftRefCount() const; /* * Increment the SoftRef count * */ - void IncrementSoftRefCount() - { - ++m_softRefCount; - } + void IncrementSoftRefCount(); /* * Decrement the SoftRef count * */ - void DecrementSoftRefCount() - { - --m_softRefCount; - } + void DecrementSoftRefCount(); /* * Get the Asset @@ -88,10 +80,7 @@ class ContainerHandle * \return The asset, can be nullptr */ [[nodiscard]] - AssetBase* GetAsset() - { - return m_asset; - } + AssetBase* GetAsset(); /* * Get the Asset @@ -99,20 +88,14 @@ class ContainerHandle * \return The asset */ [[nodiscard]] - const AssetBase* GetAsset() const - { - return m_asset; - } + const AssetBase* GetAsset() const; /* * Set the asset * * \param p_asset The asset */ - void SetAsset(AssetBase* p_asset) - { - m_asset = p_asset; - } + void SetAsset(AssetBase* p_asset); private: /* @@ -129,6 +112,16 @@ class ContainerHandle * The asset */ AssetBase* m_asset = nullptr; + + /** + * The asset container + */ + AssetContainerBase* m_assetContainer = nullptr; + + /** + * The UUID of the asset + */ + UUID m_uuid = UUID::NULL_UUID; }; /****************************************************************************************/ @@ -165,11 +158,7 @@ class AssetContainerBase * \return True if in AssetContainer, false otherwise */ [[nodiscard]] - bool Has(const UUID& p_uuid) const - { - const Entry* entry = FindEntry(p_uuid); - return entry != nullptr && entry->m_assetSlotKey.Valid(); - } + bool Has(const UUID& p_uuid) const; /* * Acquires a ContainerHandle for a given UUID @@ -179,30 +168,37 @@ class AssetContainerBase * \return A ContainerHandle for the UUID */ [[nodiscard]] - ContainerHandle* AcquireContainerHandle(const UUID& p_uuid) - { - return GetOrCreateEntry(p_uuid).m_handle; - } + ContainerHandle* AcquireContainerHandle(const UUID& p_uuid); /* * Remove an Asset from the AssetContainer * * \param p_uuid The UUID to remove */ - void Remove(const UUID& p_uuid) - { - if (Entry* entry = FindEntry(p_uuid); entry != nullptr) - { - if (entry->m_assetSlotKey.Valid()) - { - entry->m_handle->SetAsset(nullptr); - RemoveAsset(entry->m_assetSlotKey); - entry->m_assetSlotKey = SlotMapKey {}; - } - m_handles.Remove(entry->m_handleSlotKey); - m_uuidToEntry.erase(p_uuid); - } - } + void Remove(const UUID& p_uuid); + + /** + * Add an asset to be removed + * + * \param p_uuid UUID of the asset to remove + */ + void QueueForRemoval(const UUID& p_uuid); + + /** + * Remove an asset from the pending remove queue + * + * \param p_uuid UUID of the asset to cancel removal + */ + void CancelRemoval(const UUID& p_uuid); + + /** + * Process the assets that have been marked for remove + * + */ + void ProcessPendingRemovals(); + + [[nodiscard]] + bool IsMarkedForRemove(const UUID& p_uuid); AssetContainerBase& operator=(AssetContainerBase&& p_container) = delete; @@ -236,11 +232,7 @@ class AssetContainerBase * \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; - } + Entry* FindEntry(const UUID& p_uuid); /* * Finds an entry matching the Asset @@ -249,11 +241,7 @@ class AssetContainerBase * \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; - } + const Entry* FindEntry(const UUID& p_uuid) const; /* * Finds an entry matching the Asset @@ -263,17 +251,7 @@ class AssetContainerBase * \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(); - it->second.m_handle = m_handles.Get(it->second.m_handleSlotKey); - } - - return it->second; - } + Entry& GetOrCreateEntry(const UUID& p_uuid); /* * Remove an asset @@ -292,6 +270,11 @@ class AssetContainerBase * Every Asset added to the map */ ankerl::unordered_dense::map m_uuidToEntry; + + /** + * Assets queued for removal + */ + eastl::vector m_pendingRemovalUuids; }; /****************************************************************************************/ diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp index 59877c6..0d87369 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp @@ -90,6 +90,12 @@ class AssetLibrary [[nodiscard]] bool IsLoaded(const UUID& p_uuid) const; + /** + * Loop through the AssetContainers to remove the pending assets marked for removal + * + */ + void ProcessPendingRemovals(); + AssetLibrary& operator=(const AssetLibrary& p_library) = default; AssetLibrary& operator=(AssetLibrary&& p_library) = delete; diff --git a/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp index 51898d0..4d2eea2 100644 --- a/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp +++ b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp @@ -84,6 +84,40 @@ TEST_F(ContainerHandleFixture, DecrementSoftRefCount_ShouldDecrementTheSoftRefCo /****************************************************************************************/ +TEST_F(ContainerHandleFixture, DecrementHardRefCount_ShouldQueueTheAssetForRemovalWhenItReachesZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, DecrementHardRefCount_ShouldNotQueueTheAssetForRemovalIfItDoesNotReachZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, IncrementHardRefCount_ShouldCancelAPendingRemovalWhenGoingFromZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + ASSERT_TRUE(m_containerA.IsMarkedForRemove(m_uuid)); + + m_handle->IncrementHardRefCount(); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + class AssetContainerFixture: public ::testing::Test { protected: @@ -144,4 +178,78 @@ TEST_F(AssetContainerFixture, Remove_ShouldRemoveTheAsset) EXPECT_EQ(handle->GetAsset(), nullptr); EXPECT_FALSE(m_containerA.Has(uuid)); } + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, IsMarkedForRemove_ShouldReturnTrueIfTheAssetIsMarkedForDelete) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, IsMarkedForRemove_ShouldReturnFalseIfTheAssetIsNotMarkedForDelete) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, QueueForRemoval_ShouldMarkTheAssetForRemoval) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, CancelRemoval_ShouldUnmarkAQueuedAsset) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + m_containerA.CancelRemoval(uuid); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, ProcessPendingRemovals_ShouldRemoveQueuedAssets) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + m_containerA.ProcessPendingRemovals(); + + EXPECT_EQ(handle->GetAsset(), nullptr); + EXPECT_FALSE(m_containerA.Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, ProcessPendingRemovals_ShouldNotRemoveAssetsThatAreNotQueued) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.ProcessPendingRemovals(); + + EXPECT_NE(handle->GetAsset(), nullptr); + EXPECT_TRUE(m_containerA.Has(uuid)); +} } // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp b/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp index 2e96b09..22c3070 100644 --- a/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp +++ b/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp @@ -138,6 +138,38 @@ TEST_F(AssetLibraryFixture, IsLoaded_ShouldReturnFalseIfTheAssetWasRemovedFromIt /****************************************************************************************/ +TEST_F(AssetLibraryFixture, ProcessPendingRemovals_ShouldProcessRemovalsOnEveryContainer) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuidA = m_containerA.Insert()->GetAsset()->GetHeader().uuid; + const UUID uuidB = m_containerB.Insert()->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuidA); + m_containerB.QueueForRemoval(uuidB); + + m_library.ProcessPendingRemovals(); + + EXPECT_FALSE(m_containerA.Has(uuidA)); + EXPECT_FALSE(m_containerB.Has(uuidB)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, ProcessPendingRemovals_ShouldNotRemoveAssetsThatAreNotQueued) +{ + std::ignore = m_library.Insert(&m_containerA); + + const UUID uuid = m_containerA.Insert()->GetAsset()->GetHeader().uuid; + + m_library.ProcessPendingRemovals(); + + EXPECT_TRUE(m_containerA.Has(uuid)); +} + +/****************************************************************************************/ + TEST_F(AssetLibraryFixture, SetActiveAssetLibrary_ShouldSetTheActiveAssetLibraryToTheGivenLibrary) { SetActiveAssetLibrary(&m_library);