From 081e23360fed14db5a0b5de9c5ba2f437aa30f8d Mon Sep 17 00:00:00 2001 From: Romain BOULLARD Date: Mon, 24 Aug 2026 00:00:49 +0200 Subject: [PATCH] BigFile implementing DataAccessor --- Bigfoot/Sources/Engine/BigFile/BigFile.cpp | 81 +++++++ .../Sources/Engine/BigFile/BigFileSchema.sql | 20 +- .../Engine/Include/Engine/BigFile/BigFile.hpp | 42 +++- Bigfoot/Tests/Engine/BigFile/BigFile.cpp | 200 ++++++++++++++++++ 4 files changed, 330 insertions(+), 13 deletions(-) diff --git a/Bigfoot/Sources/Engine/BigFile/BigFile.cpp b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp index e2f8bc4..80dc642 100644 --- a/Bigfoot/Sources/Engine/BigFile/BigFile.cpp +++ b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp @@ -10,6 +10,7 @@ namespace Bigfoot { +#ifdef BIGFOOT_TOOLMODE BigFile::BigFile(const File& p_file) { [[maybe_unused]] @@ -59,6 +60,85 @@ BigFile::~BigFile() /****************************************************************************************/ +eastl::vector BigFile::GetAssetFlatbuffer(const UUID& p_uuid) const +{ + BigFile::Request assetRequest {*this, + "SELECT Asset.Asset FROM Asset " + "JOIN AssetMetadata ON AssetMetadata.UUID = Asset.UUID " + "WHERE Asset.UUID = ?1;"}; + assetRequest.Bind(1, p_uuid.ToBytes()); + + if (!assetRequest.Step()) + { + return {}; + } + + const eastl::span flatbuffer = assetRequest.Get(0); + return eastl::vector {flatbuffer.begin(), flatbuffer.end()}; +} + +/****************************************************************************************/ + +AssetMetadata BigFile::GetAssetMetadata(const UUID& p_uuid) const +{ + BigFile::Request assetRequest { + *this, + "SELECT UUID, Name, TypeID, TypeName, CreateTime, ModificationTime FROM AssetMetadata WHERE UUID = ?1;"}; + assetRequest.Bind(1, p_uuid.ToBytes()); + + if (!assetRequest.Step()) + { + return {}; + } + + return AssetMetadata {.m_uuid = UUID {std::span { + static_cast>(assetRequest.Get(0)).data(), + UUID::UUID_BYTE_SIZE}}, + .m_name = eastl::string {static_cast(assetRequest.Get(1))}, + .m_typeID = assetRequest.Get(2), + .m_typeName = eastl::string {static_cast(assetRequest.Get(3))}, + .m_creationTime = Time {static_cast(assetRequest.Get(4))}, + .m_modificationTime = Time {static_cast(assetRequest.Get(5))}}; +} + +/****************************************************************************************/ + +void BigFile::SaveAssetFlatbuffer(const AssetMetadata& p_metadata, const eastl::span p_flatbuffer) +{ + BeginTransaction(); + + BigFile::Request metadataRequest { + *this, + "INSERT INTO AssetMetadata (UUID, Name, TypeID, TypeName) VALUES (?1, ?2, ?3, ?4) " + "ON CONFLICT(UUID) DO UPDATE SET Name = excluded.Name, TypeID = excluded.TypeID, TypeName = excluded.TypeName;"}; + metadataRequest.Bind(1, p_metadata.m_uuid.ToBytes()); + metadataRequest.Bind(2, p_metadata.m_name); + metadataRequest.Bind(3, p_metadata.m_typeID); + metadataRequest.Bind(4, p_metadata.m_typeName); + metadataRequest.Execute(); + + BigFile::Request assetRequest {*this, + "INSERT INTO Asset (UUID, Asset) VALUES (?1, ?2) " + "ON CONFLICT(UUID) DO UPDATE SET Asset = excluded.Asset;"}; + assetRequest.Bind(1, p_metadata.m_uuid.ToBytes()); + assetRequest.Bind(2, p_flatbuffer); + assetRequest.Execute(); + + CommitTransaction(); +} + +/****************************************************************************************/ + +bool BigFile::Has(const UUID& p_uuid) const +{ + BigFile::Request request {*this, "SELECT 1 FROM AssetMetadata WHERE UUID = ?1;"}; + request.Bind(1, p_uuid.ToBytes()); + + return request.Step(); +} + +/****************************************************************************************/ + BigFile::Request::Request(const BigFile& p_bigFile, const eastl::string_view p_request): m_db(p_bigFile.m_db) { @@ -296,4 +376,5 @@ BigFile::Request::~Request() const int result = sqlite3_finalize(m_statement); CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db)); } +#endif } // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql b/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql index e329324..94454d1 100644 --- a/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql +++ b/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql @@ -1,23 +1,23 @@ PRAGMA journal_mode=WAL; PRAGMA foreign_keys = ON; -CREATE TABLE IF NOT EXISTS AssetHeader ( +CREATE TABLE IF NOT EXISTS AssetMetadata ( UUID BLOB NOT NULL UNIQUE, Name TEXT NOT NULL UNIQUE, TypeID INTEGER NOT NULL, TypeName TEXT NOT NULL, - CreateTime INTEGER NOT NULL DEFAULT(CAST(unixepoch('subsec') AS INTEGER) * 1000000), - ModificationTime INTEGER NOT NULL DEFAULT(CAST(unixepoch('subsec') AS INTEGER) * 1000000), + CreateTime INTEGER NOT NULL DEFAULT(CAST(unixepoch('subsec') * 1000000 AS INTEGER)), + ModificationTime INTEGER NOT NULL DEFAULT(CAST(unixepoch('subsec') * 1000000 AS INTEGER)), PRIMARY KEY(UUID) ); -CREATE TRIGGER IF NOT EXISTS AssetHeader_UpdateTime -AFTER UPDATE OF Name, TypeID, TypeName ON AssetHeader FOR EACH ROW +CREATE TRIGGER IF NOT EXISTS AssetMetadata_UpdateTime +AFTER UPDATE OF Name, TypeID, TypeName ON AssetMetadata FOR EACH ROW BEGIN - UPDATE AssetHeader - SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 + UPDATE AssetMetadata + SET ModificationTime = CAST(unixepoch('subsec') * 1000000 AS INTEGER) WHERE UUID = NEW.UUID; END; @@ -27,13 +27,13 @@ CREATE TABLE IF NOT EXISTS Asset ( Asset BLOB NOT NULL, PRIMARY KEY(UUID), - FOREIGN KEY(UUID) REFERENCES AssetHeader(UUID) ON DELETE CASCADE + FOREIGN KEY(UUID) REFERENCES AssetMetadata(UUID) ON DELETE CASCADE ); CREATE TRIGGER IF NOT EXISTS Asset_UpdateTime AFTER UPDATE OF Asset ON Asset FOR EACH ROW BEGIN - UPDATE AssetHeader - SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 + UPDATE AssetMetadata + SET ModificationTime = CAST(unixepoch('subsec') * 1000000 AS INTEGER) WHERE UUID = NEW.UUID; END; \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp index cb1c562..74e3208 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp @@ -6,6 +6,8 @@ *********************************************************************/ #ifndef BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP #define BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP +#include + #include #include @@ -15,7 +17,8 @@ namespace Bigfoot { -class BigFile +#ifdef BIGFOOT_TOOLMODE +class BigFile: public DataAccessor { public: /** @@ -48,6 +51,38 @@ class BigFile ~BigFile(); + /** + * Get an Asset flatbuffer + * + * \param p_uuid The asset to get the flatbuffer of + * \return The flatbuffer, can be empty if asset was not found + */ + eastl::vector GetAssetFlatbuffer(const UUID& p_uuid) const override; + + /** + * Get an Asset metadata + * + * \param p_uuid The asset to get the metadata of + * \return The metadata, default initialized if not found (ie: UUID = NULL) + */ + AssetMetadata GetAssetMetadata(const UUID& p_uuid) const override; + + /** + * Save an asset + * + * \p_metadata The metadata + * \p_flatbuffer The flatbuffer of the asset + */ + void SaveAssetFlatbuffer(const AssetMetadata& p_metadata, const eastl::span p_flatbuffer) override; + + /** + * Check if the Asset exists + * + * \param p_uuid The asset to check + * \param True if it exists, false otherwise + */ + bool Has(const UUID& p_uuid) const override; + BigFile& operator=(const BigFile& p_bigFile) = delete; BigFile& operator=(BigFile&& p_bigFile) = delete; @@ -140,7 +175,7 @@ class BigFile /* * Step through the request * - * \return True if the request can continue (get next row), false otherwise + * \return True if a row is available to read via Get(), false once the statement is done */ [[nodiscard]] bool Step(); @@ -148,7 +183,7 @@ class BigFile /* * Execute the request once * - * The number of modified rows + * \return The number of rows modified by the statement */ [[maybe_unused]] std::uint32_t Execute(); @@ -271,6 +306,7 @@ class BigFile */ sqlite3* m_db; }; +#endif } // namespace Bigfoot #endif diff --git a/Bigfoot/Tests/Engine/BigFile/BigFile.cpp b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp index e69de29..561ab05 100644 --- a/Bigfoot/Tests/Engine/BigFile/BigFile.cpp +++ b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp @@ -0,0 +1,200 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_TOOLMODE +class BigFileFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + BigFile::Request {m_bigFile, "DELETE FROM Asset;"}.Execute(); + BigFile::Request {m_bigFile, "DELETE FROM AssetMetadata;"}.Execute(); + } + + BigFile m_bigFile {File {BIGFILE_ENGINETESTS_LOCATION}}; +}; + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetFlatbuffer_ShouldReturnEmptyForNonExistentUUID) +{ + const UUID uuid; + + EXPECT_TRUE(m_bigFile.GetAssetFlatbuffer(uuid).empty()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetFlatbuffer_ShouldGetTheAssetFlatbuffer) +{ + AssetA asset; + asset.SetName("hello"); + asset.GetAsset().health = 42; + asset.GetAsset().mana = 7; + const eastl::vector flatbuffer = asset.Pack(); + + const AssetMetadata metadata {.m_uuid = asset.GetHeader().uuid, + .m_name = asset.GetHeader().name, + .m_typeID = asset.GetHeader().type_id, + .m_typeName = asset.GetHeader().type_name}; + + m_bigFile.SaveAssetFlatbuffer(metadata, flatbuffer); + + const eastl::vector savedFlatbuffer = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + EXPECT_EQ(savedFlatbuffer, flatbuffer); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetMetadata_ShouldReturnDefaultForNonExistentUUID) +{ + const UUID uuid; + + const AssetMetadata result = m_bigFile.GetAssetMetadata(uuid); + + EXPECT_EQ(result.m_uuid, UUID::NULL_UUID); + EXPECT_TRUE(result.m_name.empty()); + EXPECT_EQ(result.m_typeID, 0); + EXPECT_TRUE(result.m_typeName.empty()); + EXPECT_EQ(result.m_creationTime, Time {0}); + EXPECT_EQ(result.m_modificationTime, Time {0}); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetMetadata_ShouldGetTheAssetMetadata) +{ + AssetA asset; + asset.SetName("hello"); + asset.GetAsset().health = 42; + asset.GetAsset().mana = 7; + const eastl::vector flatbuffer = asset.Pack(); + + const AssetMetadata metadata {.m_uuid = asset.GetHeader().uuid, + .m_name = asset.GetHeader().name, + .m_typeID = asset.GetHeader().type_id, + .m_typeName = asset.GetHeader().type_name}; + + m_bigFile.SaveAssetFlatbuffer(metadata, flatbuffer); + + const AssetMetadata savedMetadata = m_bigFile.GetAssetMetadata(asset.GetHeader().uuid); + + EXPECT_EQ(savedMetadata.m_uuid, metadata.m_uuid); + EXPECT_EQ(savedMetadata.m_name, metadata.m_name); + EXPECT_EQ(savedMetadata.m_typeID, metadata.m_typeID); + EXPECT_EQ(savedMetadata.m_typeName, metadata.m_typeName); + EXPECT_GT(savedMetadata.m_creationTime, Time {0}); + EXPECT_GT(savedMetadata.m_modificationTime, Time {0}); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffer_ShouldSaveTheAsset) +{ + AssetA asset; + asset.SetName("hello"); + asset.GetAsset().health = 42; + asset.GetAsset().mana = 7; + const eastl::vector flatbuffer = asset.Pack(); + + const AssetMetadata metadata {.m_uuid = asset.GetHeader().uuid, + .m_name = asset.GetHeader().name, + .m_typeID = asset.GetHeader().type_id, + .m_typeName = asset.GetHeader().type_name}; + + m_bigFile.SaveAssetFlatbuffer(metadata, flatbuffer); + + EXPECT_TRUE(m_bigFile.Has(asset.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffer_ShouldUpdateTheDataWhenCalledTwice) +{ + const UUID uuid; + + AssetA asset; + asset.GetAsset().health = 1; + m_bigFile.SaveAssetFlatbuffer(AssetMetadata {.m_uuid = uuid, + .m_name = "hello", + .m_typeID = AssetA::GetTypeID(), + .m_typeName = eastl::string {AssetA::GetTypeName()}}, + asset.Pack()); + + const AssetMetadata savedMetadata1 = m_bigFile.GetAssetMetadata(uuid); + EXPECT_EQ(savedMetadata1.m_name, "hello"); + EXPECT_EQ(savedMetadata1.m_typeID, AssetA::GetTypeID()); + EXPECT_EQ(savedMetadata1.m_typeName, eastl::string {AssetA::GetTypeName()}); + EXPECT_EQ(savedMetadata1.m_modificationTime, savedMetadata1.m_creationTime); + + AssetA savedAsset1; + savedAsset1.Unpack(m_bigFile.GetAssetFlatbuffer(uuid)); + EXPECT_EQ(savedAsset1.GetAsset().health, 1); + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + asset.GetAsset().health = 2; + m_bigFile.SaveAssetFlatbuffer(AssetMetadata {.m_uuid = uuid, + .m_name = "world", + .m_typeID = AssetA::GetTypeID(), + .m_typeName = eastl::string {AssetA::GetTypeName()}}, + asset.Pack()); + + const AssetMetadata savedMetadata2 = m_bigFile.GetAssetMetadata(uuid); + EXPECT_EQ(savedMetadata2.m_name, "world"); + EXPECT_EQ(savedMetadata2.m_typeID, AssetA::GetTypeID()); + EXPECT_EQ(savedMetadata2.m_typeName, eastl::string {AssetA::GetTypeName()}); + EXPECT_GT(savedMetadata2.m_modificationTime, savedMetadata2.m_creationTime); + + EXPECT_LT(savedMetadata1.m_modificationTime, savedMetadata2.m_modificationTime); + + AssetA savedAsset2; + savedAsset2.Unpack(m_bigFile.GetAssetFlatbuffer(uuid)); + EXPECT_EQ(savedAsset2.GetAsset().health, 2); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Has_ShouldReturnTrueIfTheAssetExists) +{ + AssetA asset; + asset.SetName("hello"); + asset.GetAsset().health = 42; + asset.GetAsset().mana = 7; + const eastl::vector flatbuffer = asset.Pack(); + + const AssetMetadata metadata {.m_uuid = asset.GetHeader().uuid, + .m_name = asset.GetHeader().name, + .m_typeID = asset.GetHeader().type_id, + .m_typeName = asset.GetHeader().type_name}; + + m_bigFile.SaveAssetFlatbuffer(metadata, flatbuffer); + + EXPECT_TRUE(m_bigFile.Has(asset.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Has_ShouldReturnFalseIfTheAssetDoesNotExist) +{ + EXPECT_FALSE(m_bigFile.Has(UUID{})); +} +#endif +} // namespace Bigfoot