BigFile/Sasquatch #1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user