diff --git a/.clang-format b/.clang-format index 85d1168..0d12397 100644 --- a/.clang-format +++ b/.clang-format @@ -127,22 +127,26 @@ IncludeCategories: Priority: 2 - Regex: '^' + - Regex: '^' - Priority: 10 - - Regex: '^<[a-zA-Z0-9_/]+>' + - Regex: '^' + Priority: 12 + - Regex: '^<[a-zA-Z0-9_/]+\.hpp>' + Priority: 12 + - Regex: '^<[a-zA-Z0-9_/]+>' + Priority: 13 MainIncludeChar: AngleBracket \ No newline at end of file diff --git a/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp b/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp index 2511fdf..c5a589b 100644 --- a/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp +++ b/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp @@ -23,16 +23,14 @@ namespace Bigfoot { #ifdef BIGFOOT_TOOLMODE -namespace -{ using Random = effolkronium::random_static; -constexpr std::uint32_t ASSET_COUNT = 20'000; -constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; - -class Fixture: public benchmark::Fixture +class BigFileFixture: public benchmark::Fixture { - public: + protected: + static constexpr std::uint32_t ASSET_COUNT = 20'000; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + void SetUp(benchmark::State&) override { BigFile::Request {m_bigFile, "DELETE FROM Asset;"}.Execute(); @@ -63,7 +61,6 @@ class Fixture: public benchmark::Fixture m_bigFile.SaveAssetFlatbuffers(metadatas, flatbuffers); } - protected: [[nodiscard]] eastl::vector RandomBatch(const std::size_t p_batchSize) const { @@ -94,11 +91,10 @@ class Fixture: public benchmark::Fixture BigFile m_bigFile {File {BIGFILE_ENGINEBENCHMARKS_LOCATION}}; eastl::vector m_uuids; }; -} // namespace /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, GetAssetFlatbuffer)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetFlatbuffer)(benchmark::State& state) { std::int64_t bytesProcessed = 0; @@ -118,11 +114,11 @@ BENCHMARK_DEFINE_F(Fixture, GetAssetFlatbuffer)(benchmark::State& state) state.SetBytesProcessed(bytesProcessed); } -BENCHMARK_REGISTER_F(Fixture, GetAssetFlatbuffer)->Name("BigFile/GetAssetFlatbuffer"); +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetFlatbuffer)->Name("BigFile/GetAssetFlatbuffer"); /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, GetAssetFlatbuffers)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetFlatbuffers)(benchmark::State& state) { const auto batchSize = static_cast(state.range(0)); @@ -149,7 +145,7 @@ BENCHMARK_DEFINE_F(Fixture, GetAssetFlatbuffers)(benchmark::State& state) state.SetBytesProcessed(bytesProcessed); } -BENCHMARK_REGISTER_F(Fixture, GetAssetFlatbuffers) +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetFlatbuffers) ->Name("BigFile/GetAssetFlatbuffers") ->Arg(1) ->Arg(8) @@ -159,7 +155,7 @@ BENCHMARK_REGISTER_F(Fixture, GetAssetFlatbuffers) /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, GetAssetMetadata)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetMetadata)(benchmark::State& state) { for (auto _: state) { @@ -169,11 +165,11 @@ BENCHMARK_DEFINE_F(Fixture, GetAssetMetadata)(benchmark::State& state) state.SetItemsProcessed(state.iterations()); } -BENCHMARK_REGISTER_F(Fixture, GetAssetMetadata)->Name("BigFile/GetAssetMetadata"); +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetMetadata)->Name("BigFile/GetAssetMetadata"); /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, GetAssetMetadatas)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetMetadatas)(benchmark::State& state) { const auto batchSize = static_cast(state.range(0)); @@ -190,7 +186,7 @@ BENCHMARK_DEFINE_F(Fixture, GetAssetMetadatas)(benchmark::State& state) state.SetItemsProcessed(state.iterations() * batchSize); } -BENCHMARK_REGISTER_F(Fixture, GetAssetMetadatas) +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetMetadatas) ->Name("BigFile/GetAssetMetadatas") ->Arg(1) ->Arg(8) @@ -200,7 +196,7 @@ BENCHMARK_REGISTER_F(Fixture, GetAssetMetadatas) /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, SaveAssetFlatbuffer)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, SaveAssetFlatbuffer)(benchmark::State& state) { std::int64_t bytesProcessed = 0; @@ -226,11 +222,11 @@ BENCHMARK_DEFINE_F(Fixture, SaveAssetFlatbuffer)(benchmark::State& state) state.SetBytesProcessed(bytesProcessed); } -BENCHMARK_REGISTER_F(Fixture, SaveAssetFlatbuffer)->Name("BigFile/SaveAssetFlatbuffer"); +BENCHMARK_REGISTER_F(BigFileFixture, SaveAssetFlatbuffer)->Name("BigFile/SaveAssetFlatbuffer"); /****************************************************************************************/ -BENCHMARK_DEFINE_F(Fixture, SaveAssetFlatbuffers)(benchmark::State& state) +BENCHMARK_DEFINE_F(BigFileFixture, SaveAssetFlatbuffers)(benchmark::State& state) { const auto batchSize = static_cast(state.range(0)); std::int64_t bytesProcessed = 0; @@ -265,7 +261,7 @@ BENCHMARK_DEFINE_F(Fixture, SaveAssetFlatbuffers)(benchmark::State& state) state.SetBytesProcessed(bytesProcessed); } -BENCHMARK_REGISTER_F(Fixture, SaveAssetFlatbuffers) +BENCHMARK_REGISTER_F(BigFileFixture, SaveAssetFlatbuffers) ->Name("BigFile/SaveAssetFlatbuffers") ->Arg(1) ->Arg(8) diff --git a/Bigfoot/Benchmarks/Engine/CMakeLists.txt b/Bigfoot/Benchmarks/Engine/CMakeLists.txt index 9ba15ba..0bc14d1 100644 --- a/Bigfoot/Benchmarks/Engine/CMakeLists.txt +++ b/Bigfoot/Benchmarks/Engine/CMakeLists.txt @@ -10,3 +10,5 @@ target_link_libraries(${PROJECT_NAME} $) bigfoot_create_bigfile("Benchmarks/Bigfoot") + +bigfoot_create_sasquatch_info() diff --git a/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/SasquatchInfo_generated.hpp b/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/SasquatchInfo_generated.hpp new file mode 100644 index 0000000..cb4561b --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/SasquatchInfo_generated.hpp @@ -0,0 +1,22 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file SasquatchInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_ENGINEBENCHMARKS_SASQUATCHINFO_GENERATED_HPP +#define BIGFOOT_ENGINEBENCHMARKS_SASQUATCHINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_GAME + +namespace Bigfoot +{ +/* + * Sasquatch location + */ +constexpr eastl::string_view SASQUATCH_ENGINEBENCHMARKS_LOCATION {"./EngineBenchmarks-sasquatch.sasquatch"}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Benchmarks/Engine/Sasquatch/Sasquatch.cpp b/Bigfoot/Benchmarks/Engine/Sasquatch/Sasquatch.cpp new file mode 100644 index 0000000..3bd068f --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,176 @@ +/********************************************************************* + * \file Sasquatch.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_GAME +using Random = effolkronium::random_static; + +class SasquatchFixture: public benchmark::Fixture +{ + protected: + static constexpr std::uint32_t ASSET_COUNT = 20'000; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + + void SetUp([[maybe_unused]] benchmark::State& p_state) override + { + File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}.Delete(); + + eastl::vector> flatbuffers; + flatbuffers.reserve(ASSET_COUNT); + + m_uuids.clear(); + m_uuids.reserve(ASSET_COUNT); + + for (std::uint32_t i = 0; i < ASSET_COUNT; ++i) + { + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString().c_str()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + + flatbuffers.push_back(asset.Pack()); + m_uuids.push_back(asset.GetHeader().uuid); + } + + eastl::vector assets; + assets.reserve(ASSET_COUNT); + for (std::uint32_t i = 0; i < ASSET_COUNT; ++i) + { + assets.push_back(SasquatchData {.m_uuid = m_uuids[i], .m_flatbuffer = flatbuffers[i]}); + } + + std::uint64_t totalFlatbufferBytes = 0; + for (const SasquatchData& asset: assets) + { + totalFlatbufferBytes += asset.m_flatbuffer.size(); + } + + { + SasquatchWriter writer {File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}, assets.size(), totalFlatbufferBytes}; + for (const SasquatchData& asset: assets) + { + writer.WriteAsset(asset.m_uuid, asset.m_flatbuffer); + } + } + + m_sasquatch.emplace(File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}); + } + + void TearDown(benchmark::State&) override + { + m_sasquatch.reset(); + } + + [[nodiscard]] + eastl::vector RandomBatch(const std::size_t p_batchSize) const + { + eastl::vector batch; + batch.reserve(p_batchSize); + for (std::size_t i = 0; i < p_batchSize; ++i) + { + batch.push_back(m_uuids[Random::get(0, m_uuids.size() - 1)]); + } + + return batch; + } + + [[nodiscard]] + eastl::vector> RandomBatchPool(const std::size_t p_batchSize, + const std::size_t p_poolSize) const + { + eastl::vector> pool; + pool.reserve(p_poolSize); + for (std::size_t i = 0; i < p_poolSize; ++i) + { + pool.push_back(RandomBatch(p_batchSize)); + } + + return pool; + } + + eastl::optional m_sasquatch; + eastl::vector m_uuids; +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchFixture, GetAssetFlatbuffer)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + eastl::optional> flatbuffer = m_sasquatch->GetAssetFlatbuffer( + m_uuids[Random::get(0, m_uuids.size() - 1)]); + benchmark::DoNotOptimize(flatbuffer); + + if (flatbuffer.has_value()) + { + bytesProcessed += static_cast(flatbuffer->size()); + } + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SasquatchFixture, GetAssetFlatbuffer)->Name("Sasquatch/GetAssetFlatbuffer"); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchFixture, GetAssetFlatbuffers)(benchmark::State& state) +{ + const auto batchSize = static_cast(state.range(0)); + + constexpr std::size_t BATCH_POOL_SIZE = 64; + const eastl::vector> batchPool = RandomBatchPool(batchSize, BATCH_POOL_SIZE); + + std::int64_t bytesProcessed = 0; + std::size_t batchIndex = 0; + + for (auto _: state) + { + eastl::vector> flatbuffers = m_sasquatch->GetAssetFlatbuffers(batchPool[batchIndex]); + benchmark::DoNotOptimize(flatbuffers); + + for (const eastl::vector& flatbuffer: flatbuffers) + { + bytesProcessed += static_cast(flatbuffer.size()); + } + + batchIndex = (batchIndex + 1) % BATCH_POOL_SIZE; + } + + state.SetItemsProcessed(state.iterations() * batchSize); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SasquatchFixture, GetAssetFlatbuffers) + ->Name("Sasquatch/GetAssetFlatbuffers") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096); +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp b/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp index 0af610c..d0bd751 100644 --- a/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp +++ b/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp @@ -11,10 +11,7 @@ namespace Bigfoot { -namespace -{ using Random = effolkronium::random_static; -} // namespace struct MyComplexStruct { diff --git a/Bigfoot/Sources/CMakeLists.txt b/Bigfoot/Sources/CMakeLists.txt index 8eca193..392622c 100644 --- a/Bigfoot/Sources/CMakeLists.txt +++ b/Bigfoot/Sources/CMakeLists.txt @@ -1,3 +1,6 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Utils) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/System) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) \ No newline at end of file +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) +if(${TOOLMODE}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Tools) +endif() \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/Sasquatch.hpp b/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/Sasquatch.hpp new file mode 100644 index 0000000..fc23ee4 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/Sasquatch.hpp @@ -0,0 +1,214 @@ +/********************************************************************* + * \file Sasquatch.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_SASQUATCH_SASQUATCH_HPP +#define BIGFOOT_ENGINE_SASQUATCH_SASQUATCH_HPP +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Bigfoot +{ +struct SasquatchHeader +{ + static constexpr std::uint32_t MAGIC = 0x53515443; // 'SQTC' + static constexpr std::uint32_t VERSION = 1; + + std::uint32_t m_magic = MAGIC; + std::uint32_t m_version = VERSION; + std::uint64_t m_assetCount = 0; +}; + +static_assert(sizeof(SasquatchHeader) == 16); +static_assert(std::is_trivially_copyable_v); + +/****************************************************************************************/ + +struct SasquatchEntry +{ + std::array m_uuid {}; + std::size_t m_offset = 0; + std::size_t m_size = 0; +}; + +static_assert(sizeof(SasquatchEntry) == 32); +static_assert(std::is_trivially_copyable_v); + +/****************************************************************************************/ + +#if defined(BIGFOOT_TOOLMODE) || defined(BIGFOOT_BUILD_TESTS) || defined(BIGFOOT_BUILD_BENCHMARKS) +struct SasquatchData +{ + UUID m_uuid; + eastl::span m_flatbuffer; +}; + +class SasquatchWriter +{ + public: + /** + * Constructor + * Writes the header + * + * \param p_file The file to write + * \param p_assetCount The number of assets that will be written + * \param p_totalFlatbufferBytes The sum of the sizes of every flatbuffer that will be written + */ + SasquatchWriter(const File& p_file, std::uint64_t p_assetCount, std::size_t p_totalFlatbufferBytes); + + SasquatchWriter(const SasquatchWriter& p_writer) = delete; + SasquatchWriter(SasquatchWriter&& p_writer) = delete; + + /** + * Destructor + * Writes the entries + * + */ + ~SasquatchWriter(); + + /** + * Writes an asset to the file. + * + * \param p_uuid The UUID of the asset + * \param p_flatbuffer The flatbuffer of the asset + */ + void WriteAsset(const UUID& p_uuid, eastl::span p_flatbuffer); + + SasquatchWriter& operator=(const SasquatchWriter& p_writer) = delete; + SasquatchWriter& operator=(SasquatchWriter&& p_writer) = delete; + + private: + static constexpr std::uint64_t PREFETCH_BYTES = 4 * 1024 * 1024; + + /** + * The number of assets expected + */ + std::uint64_t m_assetCount; + + /** + * The offset the next WriteAsset call will write to + */ + std::size_t m_nextOffset; + + /** + * How far into m_file has already been prefetched + */ + std::size_t m_prefetchedUpTo; + + /** + * The Sasquatch file being written, mapped at its final size + */ + WritableMemoryMappedFile m_file; + + /** + * The entries + */ + eastl::vector m_entries; +}; +#endif + +/****************************************************************************************/ + +#ifdef BIGFOOT_GAME +class Sasquatch: public DataAccessor +{ + public: + /** + * Constructor + * + * \param p_file The sasquatch file + */ + Sasquatch(const File& p_file); + + Sasquatch(const Sasquatch& p_sasquatch) = delete; + Sasquatch(Sasquatch&& p_sasquatch) = delete; + + ~Sasquatch() override = default; + + /** + * Get an Asset flatbuffer + * + * \param p_uuid The asset to get the flatbuffer of + * \return The flatbuffer, or nullopt if the asset was not found + */ + eastl::optional> GetAssetFlatbuffer(const UUID& p_uuid) const override; + + /** + * Get several Asset flatbuffers + * + * \param p_uuids The assets to get the flatbuffers of + * \return The flatbuffers that were found + */ + eastl::vector> GetAssetFlatbuffers(eastl::span p_uuids) const override; + + /** + * Sasquatch only serves flatbuffers: this will always fail + * + */ + eastl::optional GetAssetMetadata(const UUID& p_uuid) const override; + + /** + * Sasquatch only serves flatbuffers: this will always fail + * + */ + eastl::vector GetAssetMetadatas(eastl::span p_uuids) const override; + + /** + * Sasquatch is read-only: this will always fail + * + */ + void SaveAssetFlatbuffer(const AssetMetadata& p_metadata, eastl::span p_flatbuffer) override; + + /** + * Sasquatch is read-only: this will always fail + * + */ + void SaveAssetFlatbuffers(eastl::span p_metadatas, + eastl::span> p_flatbuffers) 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; + + Sasquatch& operator=(const Sasquatch& p_sasquatch) = delete; + Sasquatch& operator=(Sasquatch&& p_sasquatch) = delete; + + private: + struct Entry + { + std::size_t m_offset; + std::size_t m_size; + }; + + /** + * The Sasquatch file + */ + MemoryMappedFile m_file; + + /** + * UUID to entry + */ + ankerl::unordered_dense::map m_entries; +}; +#endif +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/SasquatchInfo_generated.hpp.in b/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/SasquatchInfo_generated.hpp.in new file mode 100644 index 0000000..d54e539 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/SasquatchInfo_generated.hpp.in @@ -0,0 +1,22 @@ +@AUTO_GENERATED_COMMENT@ + +/********************************************************************* + * \file SasquatchInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_@SASQUATCH_NAME@_SASQUATCHINFO_GENERATED_HPP +#define BIGFOOT_@SASQUATCH_NAME@_SASQUATCHINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_GAME + +namespace Bigfoot +{ +/* + * Sasquatch location + */ +constexpr eastl::string_view SASQUATCH_@SASQUATCH_NAME@_LOCATION {"@SASQUATCH_LOCATION@"}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp b/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp new file mode 100644 index 0000000..bac5c34 --- /dev/null +++ b/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,232 @@ +/********************************************************************* + * \file Sasquatch.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include +#include + +namespace Bigfoot +{ +#if defined(BIGFOOT_TOOLMODE) || defined(BIGFOOT_BUILD_TESTS) || defined(BIGFOOT_BUILD_BENCHMARKS) +SasquatchWriter::SasquatchWriter(const File& p_file, + const std::uint64_t p_assetCount, + const std::size_t p_totalFlatbufferBytes): + m_assetCount(p_assetCount), + m_nextOffset(sizeof(SasquatchHeader) + p_assetCount * sizeof(SasquatchEntry)), + m_prefetchedUpTo(0), + m_file(p_file, m_nextOffset + p_totalFlatbufferBytes) +{ + m_entries.reserve(p_assetCount); + + SasquatchHeader header; + header.m_assetCount = p_assetCount; + + std::memcpy(m_file.GetData().data(), &header, sizeof(SasquatchHeader)); +} + +/****************************************************************************************/ + +SasquatchWriter::~SasquatchWriter() +{ + ASSERT(EngineAssertHandler, + m_entries.size() == m_assetCount, + "SasquatchWriter expected {} assets, only {} were added", + m_assetCount, + m_entries.size()); + + m_file.Prefetch(sizeof(SasquatchHeader), m_entries.size() * sizeof(SasquatchEntry)); + + std::memcpy(m_file.GetData().data() + sizeof(SasquatchHeader), + m_entries.data(), + m_entries.size() * sizeof(SasquatchEntry)); +} + +/****************************************************************************************/ + +void SasquatchWriter::WriteAsset(const UUID& p_uuid, const eastl::span p_flatbuffer) +{ + ASSERT(EngineAssertHandler, + m_entries.size() < m_assetCount, + "SasquatchWriter can only hold {} assets", + m_assetCount); + + const eastl::span data = m_file.GetData(); + ASSERT(EngineAssertHandler, + m_nextOffset + p_flatbuffer.size() <= data.size(), + "SasquatchWriter can only write {} bytes", + data.size()); + + const std::size_t writeEnd = m_nextOffset + p_flatbuffer.size(); + if (writeEnd > m_prefetchedUpTo) + { + const std::size_t prefetchEnd = std::min(writeEnd + PREFETCH_BYTES, data.size()); + m_file.Prefetch(m_prefetchedUpTo, prefetchEnd - m_prefetchedUpTo); + m_prefetchedUpTo = prefetchEnd; + } + + SasquatchEntry entry; + std::memcpy(entry.m_uuid.data(), p_uuid.ToBytes().data(), UUID::UUID_BYTE_SIZE); + entry.m_offset = m_nextOffset; + entry.m_size = p_flatbuffer.size(); + m_entries.push_back(entry); + + std::memcpy(data.data() + m_nextOffset, p_flatbuffer.data(), p_flatbuffer.size()); + m_nextOffset += entry.m_size; +} +#endif + +/****************************************************************************************/ + +#ifdef BIGFOOT_GAME +Sasquatch::Sasquatch(const File& p_file): + m_file(p_file) +{ + const eastl::span data = m_file.GetData(); + + CRITICAL_ASSERT(EngineAssertHandler, + data.size() >= sizeof(SasquatchHeader), + "Sasquatch file {} is smaller than its header", + p_file.GetPath()); + + SasquatchHeader header; + std::memcpy(&header, data.data(), sizeof(SasquatchHeader)); + + CRITICAL_ASSERT(EngineAssertHandler, + header.m_magic == SasquatchHeader::MAGIC, + "Sasquatch file {} has an invalid magic number (got {} / expected {})", + p_file.GetPath(), + header.m_magic, + SasquatchHeader::MAGIC); + CRITICAL_ASSERT(EngineAssertHandler, + header.m_version == SasquatchHeader::VERSION, + "Sasquatch file {} has an unsupported version (got {} / expected {})", + p_file.GetPath(), + header.m_version, + SasquatchHeader::VERSION); + + CRITICAL_ASSERT(EngineAssertHandler, + data.size() >= sizeof(SasquatchHeader) + header.m_assetCount * sizeof(SasquatchEntry), + "Sasquatch file {} index is truncated (got {} / expected at least {})", + p_file.GetPath(), + data.size(), + sizeof(SasquatchHeader) + header.m_assetCount * sizeof(SasquatchEntry)); + + eastl::vector entries(header.m_assetCount); + std::memcpy(entries.data(), data.data() + sizeof(SasquatchHeader), entries.size() * sizeof(SasquatchEntry)); + + m_entries.reserve(header.m_assetCount); + for (const SasquatchEntry& entry: entries) + { + CRITICAL_ASSERT(EngineAssertHandler, + entry.m_offset + entry.m_size <= data.size(), + "Sasquatch file {} has an out-of-bounds asset entry", + p_file.GetPath()); + + m_entries.emplace(UUID {entry.m_uuid}, Entry {.m_offset = entry.m_offset, .m_size = entry.m_size}); + } +} + +/****************************************************************************************/ + +eastl::optional> Sasquatch::GetAssetFlatbuffer(const UUID& p_uuid) const +{ + const auto it = m_entries.find(p_uuid); + if (it == m_entries.end()) + { + return eastl::nullopt; + } + + const eastl::span data = m_file.GetData(); + const eastl::span bytes {data.data() + it->second.m_offset, + static_cast(it->second.m_size)}; + return eastl::vector {bytes.begin(), bytes.end()}; +} + +/****************************************************************************************/ + +eastl::vector> Sasquatch::GetAssetFlatbuffers(const eastl::span p_uuids) const +{ + eastl::vector> flatbuffers; + if (p_uuids.empty()) + { + return flatbuffers; + } + + flatbuffers.reserve(p_uuids.size()); + + if (p_uuids.size() == 1) + { + if (eastl::optional> flatbuffer = GetAssetFlatbuffer(p_uuids[0]); + flatbuffer.has_value()) + { + flatbuffers.push_back(std::move(*flatbuffer)); + } + + return flatbuffers; + } + + ankerl::unordered_dense::set seen; + seen.reserve(p_uuids.size()); + for (const UUID& uuid: p_uuids) + { + if (!seen.insert(uuid).second) + { + continue; + } + + if (const auto it = m_entries.find(uuid); it != m_entries.end()) + { + const eastl::span data = m_file.GetData(); + const eastl::span bytes {data.data() + it->second.m_offset, + static_cast(it->second.m_size)}; + flatbuffers.emplace_back(bytes.begin(), bytes.end()); + } + } + + return flatbuffers; +} + +/****************************************************************************************/ + +eastl::optional Sasquatch::GetAssetMetadata(const UUID&) const +{ + ASSERT(EngineAssertHandler, false, "Sasquatch does not support metadata queries, only flatbuffers"); + return eastl::nullopt; +} + +/****************************************************************************************/ + +eastl::vector Sasquatch::GetAssetMetadatas(const eastl::span) const +{ + ASSERT(EngineAssertHandler, false, "Sasquatch does not support metadata queries, only flatbuffers"); + return {}; +} + +/****************************************************************************************/ + +void Sasquatch::SaveAssetFlatbuffer(const AssetMetadata&, eastl::span) +{ + ASSERT(EngineAssertHandler, false, "Sasquatch is read-only, it cannot save assets"); +} + +/****************************************************************************************/ + +void Sasquatch::SaveAssetFlatbuffers(eastl::span, eastl::span>) +{ + ASSERT(EngineAssertHandler, false, "Sasquatch is read-only, it cannot save assets"); +} + +/****************************************************************************************/ + +bool Sasquatch::Has(const UUID& p_uuid) const +{ + return m_entries.find(p_uuid) != m_entries.end(); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Tools/CMakeLists.txt b/Bigfoot/Sources/Tools/CMakeLists.txt new file mode 100644 index 0000000..a996a00 --- /dev/null +++ b/Bigfoot/Sources/Tools/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/SasquatchBaker) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Executables/SasquatchBaker) diff --git a/Bigfoot/Sources/Tools/Executables/SasquatchBaker/CMakeLists.txt b/Bigfoot/Sources/Tools/Executables/SasquatchBaker/CMakeLists.txt new file mode 100644 index 0000000..b98d3b3 --- /dev/null +++ b/Bigfoot/Sources/Tools/Executables/SasquatchBaker/CMakeLists.txt @@ -0,0 +1,33 @@ +project(SasquatchBakerCLI) + +add_executable(${PROJECT_NAME}) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + BigfootCompileAndLinkFlags + bfg::lyra + $) + +file(GLOB_RECURSE _SOURCES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp +) + +file(GLOB_RECURSE _HEADERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp +) + +target_sources(${PROJECT_NAME} + PRIVATE + ${_SOURCES} + ${_HEADERS} +) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${_SOURCES} ${_HEADERS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Bigfoot/Tools/Executables) +set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME SasquatchBaker) diff --git a/Bigfoot/Sources/Tools/Executables/SasquatchBaker/main.cpp b/Bigfoot/Sources/Tools/Executables/SasquatchBaker/main.cpp new file mode 100644 index 0000000..b36d07f --- /dev/null +++ b/Bigfoot/Sources/Tools/Executables/SasquatchBaker/main.cpp @@ -0,0 +1,49 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +int main(int argc, char** argv) +{ + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime logLifetime); + + bool showHelp = false; + std::string bigFilePath; + std::string outputPath; + + auto cli = lyra::help(showHelp) | + lyra::opt(bigFilePath, "bigfile")["-b"]["--bigfile"]("The BigFile to bake (must exist)").required() | + lyra::opt(outputPath, "output")["-o"]["--output"]("The Sasquatch file to write").required(); + + const lyra::parse_result result = cli.parse({argc, argv}); + if (!result) + { + std::cerr << result.message() << "\n\n" << cli << std::endl; + return 1; + } + + if (showHelp) + { + std::cout << cli << std::endl; + return 0; + } + + const Bigfoot::BigFile bigFile {Bigfoot::File {eastl::string_view {bigFilePath.c_str()}}}; + Bigfoot::Bake(bigFile, Bigfoot::File {eastl::string_view {outputPath.c_str()}}); + + return 0; +} diff --git a/Bigfoot/Sources/Tools/SasquatchBaker/CMakeLists.txt b/Bigfoot/Sources/Tools/SasquatchBaker/CMakeLists.txt new file mode 100644 index 0000000..eaee168 --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/CMakeLists.txt @@ -0,0 +1,18 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}) + +set(PublicDependencies) +set(PrivateDependencies) +set(BigfootPublicDependencies + Engine) +set(BigfootPrivateDependencies + Utils) + +bigfoot_create_package_lib( + "${PublicDependencies}" + "${PrivateDependencies}" + "${BigfootPublicDependencies}" + "${BigfootPrivateDependencies}" + "Tools") + +bigfoot_create_logger() diff --git a/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBaker.hpp b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBaker.hpp new file mode 100644 index 0000000..5fc83e3 --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBaker.hpp @@ -0,0 +1,24 @@ +/********************************************************************* + * \file SasquatchBaker.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_TOOLS_SASQUATCHBAKER_SASQUATCHBAKER_HPP +#define BIGFOOT_TOOLS_SASQUATCHBAKER_SASQUATCHBAKER_HPP +#include + +#include + +namespace Bigfoot +{ +/** + * Bake every Asset of a BigFile into a Sasquatch file + * + * \param p_bigFile The BigFile to bake + * \param p_outputFile The file to write to + */ +void Bake(const BigFile& p_bigFile, const File& p_outputFile); +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerAssertHandler.hpp b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerAssertHandler.hpp new file mode 100644 index 0000000..fbde209 --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerAssertHandler.hpp @@ -0,0 +1,20 @@ +/********************************************************************* + * \file SasquatchBakerAssertHandler.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_TOOLS_SASQUATCHBAKER_SASQUATCHBAKERASSERTHANDLER_HPP +#define BIGFOOT_TOOLS_SASQUATCHBAKER_SASQUATCHBAKERASSERTHANDLER_HPP +#include + +#include + +#if defined BIGFOOT_NOT_OPTIMIZED + +namespace Bigfoot +{ +using SasquatchBakerAssertHandler = AssertHandler; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerLogger_generated.hpp b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerLogger_generated.hpp new file mode 100644 index 0000000..f646906 --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/Include/SasquatchBaker/SasquatchBakerLogger_generated.hpp @@ -0,0 +1,21 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file SasquatchBakerLogger.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_SASQUATCHBAKERLOGGER_GENERATED_HPP +#define BIGFOOT_SASQUATCHBAKERLOGGER_GENERATED_HPP +#include + +#if defined BIGFOOT_NOT_OPTIMIZED + +namespace Bigfoot +{ +/* + * Logger + */ +inline Log::LoggerInfo SASQUATCHBAKER_LOGGER {"SASQUATCHBAKER_LOGGER", Flat::LogLevel::Trace}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Sources/Tools/SasquatchBaker/SasquatchBaker.cpp b/Bigfoot/Sources/Tools/SasquatchBaker/SasquatchBaker.cpp new file mode 100644 index 0000000..8b1508d --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/SasquatchBaker.cpp @@ -0,0 +1,54 @@ +/********************************************************************* + * \file SasquatchBaker.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +namespace Bigfoot +{ +void Bake(const BigFile& p_bigFile, const File& p_outputFile) +{ + std::uint64_t assetCount = 0; + { + BigFile::Request countRequest {p_bigFile, "SELECT COUNT(*) FROM Asset;"}; + [[maybe_unused]] + const bool hasCount = countRequest.Step(); + ASSERT(SasquatchBakerAssertHandler, hasCount, "Failed to count the assets of the BigFile"); + assetCount = countRequest.Get(0); + } + + std::uint64_t totalFlatbufferBytes = 0; + { + BigFile::Request sizeRequest {p_bigFile, "SELECT COALESCE(SUM(LENGTH(Asset)), 0) FROM Asset;"}; + [[maybe_unused]] + const bool hasSize = sizeRequest.Step(); + ASSERT(SasquatchBakerAssertHandler, hasSize, "Failed to sum the asset sizes of the BigFile"); + totalFlatbufferBytes = sizeRequest.Get(0); + } + + SasquatchWriter writer {p_outputFile, assetCount, totalFlatbufferBytes}; + + BigFile::Request request {p_bigFile, "SELECT UUID, Asset FROM Asset;"}; + while (request.Step()) + { + const eastl::span uuidBytes = request.Get(0); + const eastl::span flatbuffer = request.Get(1); + + ASSERT(SasquatchBakerAssertHandler, + uuidBytes.size() == UUID::UUID_BYTE_SIZE, + "Unexpected UUID blob size in BigFile (got {} / expected {})", + uuidBytes.size(), + UUID::UUID_BYTE_SIZE); + + writer.WriteAsset( + UUID {{std::span {uuidBytes.data(), UUID::UUID_BYTE_SIZE}}}, + flatbuffer); + } +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/CMakeLists.txt b/Bigfoot/Tests/CMakeLists.txt index cbbb5ea..550d037 100644 --- a/Bigfoot/Tests/CMakeLists.txt +++ b/Bigfoot/Tests/CMakeLists.txt @@ -2,4 +2,7 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Misc) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Utils) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/System) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) \ No newline at end of file +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) +if(${TOOLMODE}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Tools) +endif() \ No newline at end of file diff --git a/Bigfoot/Tests/Engine/CMakeLists.txt b/Bigfoot/Tests/Engine/CMakeLists.txt index 89333e5..bc71b72 100644 --- a/Bigfoot/Tests/Engine/CMakeLists.txt +++ b/Bigfoot/Tests/Engine/CMakeLists.txt @@ -10,4 +10,6 @@ bigfoot_create_package_tests( "${BigfootDependencies}" "") -bigfoot_create_bigfile("Tests/Bigfoot") \ No newline at end of file +bigfoot_create_bigfile("Tests/Bigfoot") + +bigfoot_create_sasquatch_info() \ No newline at end of file diff --git a/Bigfoot/Tests/Engine/Include/EngineTests/SasquatchInfo_generated.hpp b/Bigfoot/Tests/Engine/Include/EngineTests/SasquatchInfo_generated.hpp new file mode 100644 index 0000000..5ba2982 --- /dev/null +++ b/Bigfoot/Tests/Engine/Include/EngineTests/SasquatchInfo_generated.hpp @@ -0,0 +1,22 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file SasquatchInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_ENGINETESTS_SASQUATCHINFO_GENERATED_HPP +#define BIGFOOT_ENGINETESTS_SASQUATCHINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_GAME + +namespace Bigfoot +{ +/* + * Sasquatch location + */ +constexpr eastl::string_view SASQUATCH_ENGINETESTS_LOCATION {"./EngineTests-sasquatch.sasquatch"}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Tests/Engine/Sasquatch/Sasquatch.cpp b/Bigfoot/Tests/Engine/Sasquatch/Sasquatch.cpp new file mode 100644 index 0000000..17e95c6 --- /dev/null +++ b/Bigfoot/Tests/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,118 @@ +/********************************************************************* + * \file Sasquatch.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include + +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_GAME +class SasquatchFixture: public ::testing::Test +{ + protected: + void SetUp() + { + File {SASQUATCH_ENGINETESTS_LOCATION}.Delete(); + + m_assetA.SetName("hello"); + m_flatbufferA = m_assetA.Pack(); + + m_assetB.SetName("world"); + m_flatbufferB = m_assetB.Pack(); + + const eastl::vector assets { + SasquatchData {.m_uuid = m_assetA.GetHeader().uuid, .m_flatbuffer = m_flatbufferA}, + SasquatchData {.m_uuid = m_assetB.GetHeader().uuid, .m_flatbuffer = m_flatbufferB}}; + + std::uint64_t totalFlatbufferBytes = 0; + for (const SasquatchData& asset: assets) + { + totalFlatbufferBytes += asset.m_flatbuffer.size(); + } + + { + SasquatchWriter writer {File {SASQUATCH_ENGINETESTS_LOCATION}, assets.size(), totalFlatbufferBytes}; + for (const SasquatchData& asset: assets) + { + writer.WriteAsset(asset.m_uuid, asset.m_flatbuffer); + } + } + + m_sasquatch.emplace(File {SASQUATCH_ENGINETESTS_LOCATION}); + } + + AssetA m_assetA; + AssetB m_assetB; + + eastl::vector m_flatbufferA; + eastl::vector m_flatbufferB; + + eastl::optional m_sasquatch; +}; + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffer_ShouldReturnNulloptForNonExistentUUID) +{ + EXPECT_FALSE(m_sasquatch->GetAssetFlatbuffer(UUID {}).has_value()); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffer_ShouldGetTheAssetFlatbuffer) +{ + const eastl::optional> savedFlatbuffer = m_sasquatch->GetAssetFlatbuffer( + m_assetA.GetHeader().uuid); + ASSERT_TRUE(savedFlatbuffer.has_value()); + EXPECT_EQ(*savedFlatbuffer, m_flatbufferA); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffers_ShouldOmitUUIDsThatDoNotExist) +{ + const eastl::vector uuids {m_assetA.GetHeader().uuid, UUID {}, m_assetB.GetHeader().uuid}; + const eastl::vector> flatbuffers = m_sasquatch->GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 2); + EXPECT_TRUE((flatbuffers[0] == m_flatbufferA && flatbuffers[1] == m_flatbufferB) || + (flatbuffers[0] == m_flatbufferB && flatbuffers[1] == m_flatbufferA)); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffers_ShouldNotDuplicateResultsForADuplicateUUID) +{ + const eastl::vector uuids {m_assetA.GetHeader().uuid, m_assetA.GetHeader().uuid}; + const eastl::vector> flatbuffers = m_sasquatch->GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 1); + EXPECT_EQ(flatbuffers[0], m_flatbufferA); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, Has_ShouldReturnTrueIfTheAssetExists) +{ + EXPECT_TRUE(m_sasquatch->Has(m_assetA.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, Has_ShouldReturnFalseIfTheAssetDoesNotExist) +{ + EXPECT_FALSE(m_sasquatch->Has(UUID {})); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Tools/CMakeLists.txt b/Bigfoot/Tests/Tools/CMakeLists.txt new file mode 100644 index 0000000..4f25147 --- /dev/null +++ b/Bigfoot/Tests/Tools/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/SasquatchBaker) diff --git a/Bigfoot/Tests/Tools/SasquatchBaker/CMakeLists.txt b/Bigfoot/Tests/Tools/SasquatchBaker/CMakeLists.txt new file mode 100644 index 0000000..f3d4afd --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/CMakeLists.txt @@ -0,0 +1,13 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}Tests) + +set(Dependencies) +set(BigfootDependencies + AssetsForTesting) + +bigfoot_create_package_tests( + "${Dependencies}" + "${BigfootDependencies}" + "Tools") + +bigfoot_create_bigfile("Tests/Tools") diff --git a/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp new file mode 100644 index 0000000..3a23bdd --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp @@ -0,0 +1,113 @@ +/********************************************************************* + * \file SasquatchBaker.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +class SasquatchBakerFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + BigFile::Request {m_bigFile, "DELETE FROM Asset;"}.Execute(); + BigFile::Request {m_bigFile, "DELETE FROM AssetMetadata;"}.Execute(); + m_sasquatch.Delete(); + } + + [[nodiscard]] + SasquatchHeader ReadHeader(const eastl::vector& p_data) + { + SasquatchHeader header; + std::memcpy(&header, p_data.data(), sizeof(SasquatchHeader)); + return header; + } + + [[nodiscard]] + eastl::optional FindEntry(const eastl::vector& p_data, const UUID& p_uuid) + { + const SasquatchHeader header = ReadHeader(p_data); + + for (std::uint64_t i = 0; i < header.m_assetCount; ++i) + { + SasquatchEntry entry; + std::memcpy(&entry, + p_data.data() + sizeof(SasquatchHeader) + i * sizeof(SasquatchEntry), + sizeof(SasquatchEntry)); + + if (UUID {entry.m_uuid} == p_uuid) + { + return entry; + } + } + + return eastl::nullopt; + } + + static constexpr eastl::string_view SASQUATCH_SASQUATCHBAKERTESTS_LOCATION { + "./SasquatchBakerTests-sasquatch.sasquatch"}; + BigFile m_bigFile {File {BIGFILE_SASQUATCHBAKERTESTS_LOCATION}}; + File m_sasquatch {SASQUATCH_SASQUATCHBAKERTESTS_LOCATION}; +}; + +/****************************************************************************************/ + +TEST_F(SasquatchBakerFixture, Bake_ShouldWriteAnEmptyFileWhenTheBigFileIsEmpty) +{ + Bake(m_bigFile, m_sasquatch); + + const eastl::vector data = m_sasquatch.Read(0, m_sasquatch.GetSize()); + ASSERT_EQ(data.size(), sizeof(SasquatchHeader)); + + const SasquatchHeader header = ReadHeader(data); + EXPECT_EQ(header.m_magic, SasquatchHeader::MAGIC); + EXPECT_EQ(header.m_version, SasquatchHeader::VERSION); + EXPECT_EQ(header.m_assetCount, 0); +} + +/****************************************************************************************/ + +TEST_F(SasquatchBakerFixture, Bake_ShouldWriteEveryAssetOfTheBigFile) +{ + AssetA assetA; + assetA.SetName("hello"); + m_bigFile.SaveAssetFlatbuffer(AssetMetadata {.m_uuid = assetA.GetHeader().uuid, + .m_name = assetA.GetHeader().name, + .m_typeID = assetA.GetHeader().type_id, + .m_typeName = assetA.GetHeader().type_name}, + assetA.Pack()); + + AssetA assetB; + assetB.SetName("world"); + m_bigFile.SaveAssetFlatbuffer(AssetMetadata {.m_uuid = assetB.GetHeader().uuid, + .m_name = assetB.GetHeader().name, + .m_typeID = assetB.GetHeader().type_id, + .m_typeName = assetB.GetHeader().type_name}, + assetB.Pack()); + + Bake(m_bigFile, m_sasquatch); + + const eastl::vector data = m_sasquatch.Read(0, m_sasquatch.GetSize()); + const SasquatchHeader header = ReadHeader(data); + EXPECT_EQ(header.m_assetCount, 2); + + EXPECT_TRUE(FindEntry(data, assetA.GetHeader().uuid).has_value()); + EXPECT_TRUE(FindEntry(data, assetB.GetHeader().uuid).has_value()); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBakerTests/main.cpp b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBakerTests/main.cpp new file mode 100644 index 0000000..ec88d0c --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBakerTests/main.cpp @@ -0,0 +1,19 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include +#include + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime m_lifetime); + + return RUN_ALL_TESTS(); +} diff --git a/CMake/Package.cmake b/CMake/Package.cmake index 566875a..ea6baac 100644 --- a/CMake/Package.cmake +++ b/CMake/Package.cmake @@ -97,7 +97,9 @@ function(bigfoot_create_package_tests PackageDependencies PackageBigfootDependen ) include(GoogleTest) - gtest_discover_tests(${PROJECT_NAME} XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/TestResults/) + gtest_discover_tests(${PROJECT_NAME} + WORKING_DIRECTORY $ + XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/TestResults/) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src/ FILES ${_SOURCES} ${_HEADERS} ${_OTHERS}) diff --git a/CMake/Utils.cmake b/CMake/Utils.cmake index ca0166f..a953366 100644 --- a/CMake/Utils.cmake +++ b/CMake/Utils.cmake @@ -38,6 +38,16 @@ function(bigfoot_create_bigfile ParentFolder) ) endfunction() +function(bigfoot_create_sasquatch_info) + string(TOUPPER ${PROJECT_NAME} SASQUATCH_NAME) + set(SASQUATCH_LOCATION "./${PROJECT_NAME}-sasquatch.sasquatch") + configure_file( + ${CMAKE_SOURCE_DIR}/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/SasquatchInfo_generated.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/Include/${PROJECT_NAME}/SasquatchInfo_generated.hpp + @ONLY + ) +endfunction() + function(_bigfoot_collect_includes_recursive Target UseAllLinks) get_property(_Visited GLOBAL PROPERTY _BIGFOOT_VISITED_TARGETS) if("${Target}" IN_LIST _Visited)