BigFile implementing DataAccessor
Bigfoot / Sonarqube (push) Skipped
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 13m52s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 14m16s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 14m17s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 13m52s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 14m22s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 14m48s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 15m0s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 14m30s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 15m27s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 15m5s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 16m44s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 14m45s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 15m25s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 15m28s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 14m57s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 17m22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 17m23s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 15m12s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 16m53s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 15m1s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 16m9s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 16m7s
Bigfoot / Clang Format Checks (push) Failing after 10s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 16m20s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 16m1s

This commit is contained in:
2026-08-24 00:00:49 +02:00
parent eb22baacf9
commit 081e23360f
4 changed files with 330 additions and 13 deletions
@@ -10,6 +10,7 @@
namespace Bigfoot namespace Bigfoot
{ {
#ifdef BIGFOOT_TOOLMODE
BigFile::BigFile(const File& p_file) BigFile::BigFile(const File& p_file)
{ {
[[maybe_unused]] [[maybe_unused]]
@@ -59,6 +60,85 @@ BigFile::~BigFile()
/****************************************************************************************/ /****************************************************************************************/
eastl::vector<std::byte> 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<const std::byte> flatbuffer = assetRequest.Get(0);
return eastl::vector<std::byte> {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<const std::byte, UUID::UUID_BYTE_SIZE> {
static_cast<eastl::span<const std::byte>>(assetRequest.Get(0)).data(),
UUID::UUID_BYTE_SIZE}},
.m_name = eastl::string {static_cast<eastl::string_view>(assetRequest.Get(1))},
.m_typeID = assetRequest.Get(2),
.m_typeName = eastl::string {static_cast<eastl::string_view>(assetRequest.Get(3))},
.m_creationTime = Time {static_cast<std::uint64_t>(assetRequest.Get(4))},
.m_modificationTime = Time {static_cast<std::uint64_t>(assetRequest.Get(5))}};
}
/****************************************************************************************/
void BigFile::SaveAssetFlatbuffer(const AssetMetadata& p_metadata, const eastl::span<const std::byte> 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): BigFile::Request::Request(const BigFile& p_bigFile, const eastl::string_view p_request):
m_db(p_bigFile.m_db) m_db(p_bigFile.m_db)
{ {
@@ -296,4 +376,5 @@ BigFile::Request::~Request()
const int result = sqlite3_finalize(m_statement); const int result = sqlite3_finalize(m_statement);
CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db)); CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db));
} }
#endif
} // namespace Bigfoot } // namespace Bigfoot
@@ -1,23 +1,23 @@
PRAGMA journal_mode=WAL; PRAGMA journal_mode=WAL;
PRAGMA foreign_keys = ON; PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS AssetHeader ( CREATE TABLE IF NOT EXISTS AssetMetadata (
UUID BLOB NOT NULL UNIQUE, UUID BLOB NOT NULL UNIQUE,
Name TEXT NOT NULL UNIQUE, Name TEXT NOT NULL UNIQUE,
TypeID INTEGER NOT NULL, TypeID INTEGER NOT NULL,
TypeName TEXT NOT NULL, TypeName TEXT NOT NULL,
CreateTime 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') AS INTEGER) * 1000000), ModificationTime INTEGER NOT NULL DEFAULT(CAST(unixepoch('subsec') * 1000000 AS INTEGER)),
PRIMARY KEY(UUID) PRIMARY KEY(UUID)
); );
CREATE TRIGGER IF NOT EXISTS AssetHeader_UpdateTime CREATE TRIGGER IF NOT EXISTS AssetMetadata_UpdateTime
AFTER UPDATE OF Name, TypeID, TypeName ON AssetHeader FOR EACH ROW AFTER UPDATE OF Name, TypeID, TypeName ON AssetMetadata FOR EACH ROW
BEGIN BEGIN
UPDATE AssetHeader UPDATE AssetMetadata
SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 SET ModificationTime = CAST(unixepoch('subsec') * 1000000 AS INTEGER)
WHERE UUID = NEW.UUID; WHERE UUID = NEW.UUID;
END; END;
@@ -27,13 +27,13 @@ CREATE TABLE IF NOT EXISTS Asset (
Asset BLOB NOT NULL, Asset BLOB NOT NULL,
PRIMARY KEY(UUID), 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 CREATE TRIGGER IF NOT EXISTS Asset_UpdateTime
AFTER UPDATE OF Asset ON Asset FOR EACH ROW AFTER UPDATE OF Asset ON Asset FOR EACH ROW
BEGIN BEGIN
UPDATE AssetHeader UPDATE AssetMetadata
SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 SET ModificationTime = CAST(unixepoch('subsec') * 1000000 AS INTEGER)
WHERE UUID = NEW.UUID; WHERE UUID = NEW.UUID;
END; END;
@@ -6,6 +6,8 @@
*********************************************************************/ *********************************************************************/
#ifndef BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP #ifndef BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP
#define BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP #define BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP
#include <Engine/DataAccessor/DataAccessor.hpp>
#include <System/File.hpp> #include <System/File.hpp>
#include <Utils/TaggedType.hpp> #include <Utils/TaggedType.hpp>
@@ -15,7 +17,8 @@
namespace Bigfoot namespace Bigfoot
{ {
class BigFile #ifdef BIGFOOT_TOOLMODE
class BigFile: public DataAccessor
{ {
public: public:
/** /**
@@ -48,6 +51,38 @@ class BigFile
~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<std::byte> 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<const std::byte> 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=(const BigFile& p_bigFile) = delete;
BigFile& operator=(BigFile&& p_bigFile) = delete; BigFile& operator=(BigFile&& p_bigFile) = delete;
@@ -140,7 +175,7 @@ class BigFile
/* /*
* Step through the request * 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]] [[nodiscard]]
bool Step(); bool Step();
@@ -148,7 +183,7 @@ class BigFile
/* /*
* Execute the request once * Execute the request once
* *
* The number of modified rows * \return The number of rows modified by the statement
*/ */
[[maybe_unused]] [[maybe_unused]]
std::uint32_t Execute(); std::uint32_t Execute();
@@ -271,6 +306,7 @@ class BigFile
*/ */
sqlite3* m_db; sqlite3* m_db;
}; };
#endif
} // namespace Bigfoot } // namespace Bigfoot
#endif #endif
+200
View File
@@ -0,0 +1,200 @@
/*********************************************************************
* \file BigFile.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/BigFile/BigFile.hpp>
#include <System/File.hpp>
#include <EngineTests/BigFileInfo_generated.hpp>
#include <AssetsForTesting/Asset/AssetA.hpp>
#include <gtest/gtest.h>
#include <chrono>
#include <thread>
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<std::byte> 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<std::byte> 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<std::byte> 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<std::byte> 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<std::byte> 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