BigFile/Sasquatch #1

Merged
rboullard merged 100 commits from BigFile into Development 2026-09-01 16:32:02 +00:00
4 changed files with 330 additions and 13 deletions
Showing only changes of commit 081e23360f - Show all commits
@@ -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<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):
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
@@ -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;
@@ -6,6 +6,8 @@
*********************************************************************/
#ifndef BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP
#define BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP
#include <Engine/DataAccessor/DataAccessor.hpp>
#include <System/File.hpp>
#include <Utils/TaggedType.hpp>
@@ -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<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=(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
+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