diff --git a/.clang-format b/.clang-format index e93c306..94ddad8 100644 --- a/.clang-format +++ b/.clang-format @@ -51,7 +51,7 @@ CompactNamespaces: false Cpp11BracedListStyle: true EmptyLineBeforeAccessModifier: Always FixNamespaceComments: true -IncludeBlocks: Preserve +IncludeBlocks: Regroup IndentCaseBlocks: true IndentCaseLabels: false IndentPPDirectives: None @@ -119,4 +119,45 @@ SpaceAroundPointerQualifiers: Default SpaceBeforeCpp11BracedList: true SpaceInEmptyBlock: true PenaltyBreakAssignment: 200 -KeepEmptyLinesAtTheStartOfBlocks: false \ No newline at end of file +KeepEmptyLinesAtTheStartOfBlocks: false +IncludeCategories: + - Regex: '^' + Priority: 13 + - Regex: '^<[a-zA-Z0-9_/]+\.hpp>' + Priority: 13 + - Regex: '^<[a-zA-Z0-9_/]+>' + Priority: 14 +MainIncludeChar: AngleBracket +Macros: + - 'BIGFOOT_OPTIMIZED_ONLY(x)=x' + - 'BIGFOOT_OPTIMIZED_ONLY(x,y)=x,y' + - 'BIGFOOT_NOT_OPTIMIZED_ONLY(x)=x' + - 'BIGFOOT_NOT_OPTIMIZED_ONLY(x,y)=x,y' + - 'BIGFOOT_TOOLMODE_ONLY(x)=x' + - 'BIGFOOT_TOOLMODE_ONLY(x,y)=x,y' + - 'BIGFOOT_GAME_ONLY(x)=x' + - 'BIGFOOT_GAME_ONLY(x,y)=x,y' diff --git a/.clang-tidy b/.clang-tidy index d28ed7a..025a0bd 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,9 +11,12 @@ Checks: > portability-*, -modernize-use-trailing-return-type, + -modernize-use-auto, -readability-avoid-const-params-in-decls, -cppcoreguidelines-macro-usage, - -cppcoreguidelines-avoid-do-while + -cppcoreguidelines-avoid-do-while, + -cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, + -cppcoreguidelines-pro-bounds-pointer-arithmetic ExcludeHeaderFilterRegex: '_generated.*' diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 46a94d4..b50d3c9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,4 +1,4 @@ { "name": "Bigfoot", - "image": "git.romainboullard.com/bigfootdev/linuxbigfootbuilder:main" + "image": "git.romainboullard.com/bigfootdev/linuxcppbuilder:development" } diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 8c52204..cce2395 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -6,20 +6,22 @@ on: - '**' workflow_dispatch: -env: - CCACHE_BASEDIR: ${{ github.workspace }} - jobs: build-and-test: runs-on: ubuntu-latest timeout-minutes: 120 container: - image: git.romainboullard.com/bigfootdev/linuxbigfootbuilder:main + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:development + strategy: matrix: build_type: ["Debug", "RelWithDebInfo", "Release"] unity_build: ["ON", "OFF"] - name: "Build & Test ${{ matrix.build_type }} (Unity Build: ${{ matrix.unity_build }})" + conan_profile: ["./ConanProfiles/host/clang", "./ConanProfiles/host/clang_asan"] + toolmode: + - { value: "True", name: "toolmode" } + - { value: "False", name: "game" } + name: "Build & Test ${{ matrix.build_type }} with ${{ matrix.conan_profile }} (Unity Build: ${{ matrix.unity_build }}, Variant: ${{ matrix.toolmode.name }})" steps: - name: Install Node.js run: apt-get update && apt-get install -y nodejs @@ -29,28 +31,26 @@ jobs: with: submodules: recursive - - name: Show ccache stats before - run: ccache --zero-stats - - name: Build run: | - conan install . --deployer=full_deploy --deployer-folder=build --remote=bigfootpackages -pr:h=clang -pr:b=clang --build=missing -s build_type=${{ matrix.build_type }} -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=False -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - cmake -S . -B ./build/${{ matrix.build_type }} --toolchain ./build/${{ matrix.build_type }}/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_UNITY_BUILD=${{ matrix.unity_build }} -G "Ninja" - cmake --build build/${{ matrix.build_type }} --parallel $(nproc) - - - name: Show ccache stats after - run: ccache --show-stats + conan install . --remote=bigfootpackages -pr:h=${{ matrix.conan_profile }} -pr:b=./ConanProfiles/build/clang --build=missing -s build_type=${{ matrix.build_type }} -o bigfoot/*:build_tests=True -o bigfoot/*:build_benchmarks=True -o bigfoot/*:tracy=False -o bigfoot/*:vulkan=True -o bigfoot/*:toolmode=${{ matrix.toolmode.value }} -of build-${{ matrix.toolmode.name }} + . ./build-${{ matrix.toolmode.name }}/build/${{ matrix.build_type }}/generators/conanbuild.sh + cmake -S . -B ./build-${{ matrix.toolmode.name }}/${{ matrix.build_type }} --toolchain ./build-${{ matrix.toolmode.name }}/build/${{ matrix.build_type }}/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_UNITY_BUILD=${{ matrix.unity_build }} -G "Ninja" + cmake --build build-${{ matrix.toolmode.name }}/${{ matrix.build_type }} --parallel $(nproc) + . ./build-${{ matrix.toolmode.name }}/build/${{ matrix.build_type }}/generators/deactivate_conanbuild.sh - name: Unit Tests run: | - cd ./build/${{ matrix.build_type }} + cd ./build-${{ matrix.toolmode.name }}/${{ matrix.build_type }} + . ../../build-${{ matrix.toolmode.name }}/build/${{ matrix.build_type }}/generators/conanbuild.sh xvfb-run ctest . --output-on-failure + . ../../build-${{ matrix.toolmode.name }}/build/${{ matrix.build_type }}/generators/deactivate_conanbuild.sh clang-format: runs-on: ubuntu-latest timeout-minutes: 120 container: - image: git.romainboullard.com/bigfootdev/linuxbigfootbuilder:main + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:main name: "Clang Format Checks" steps: - name: Install Node.js diff --git a/.gitea/workflows/sonarqube.yml b/.gitea/workflows/sonarqube.yml index 6e8c798..a642d6e 100644 --- a/.gitea/workflows/sonarqube.yml +++ b/.gitea/workflows/sonarqube.yml @@ -5,13 +5,18 @@ on: branches: - main - Development + pull_request: + branches: + - main + - Development jobs: build-and-test: runs-on: ubuntu-latest timeout-minutes: 120 container: - image: git.romainboullard.com/bigfootdev/linuxbigfootbuilder:main + image: git.romainboullard.com/bigfootdev/linuxcppbuilder:development + name: "Sonarqube" steps: - name: Install Node.js @@ -22,26 +27,56 @@ jobs: with: fetch-depth: 0 submodules: recursive + ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Generate + - name: Build run: | - conan install . --deployer=full_deploy --deployer-folder=build --remote=bigfootpackages -pr:h=clang_coverage -pr:b=clang_coverage --build=missing -s build_type=Debug -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=False -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - cmake -S . -B ./build/Debug --toolchain ./build/Debug/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -G "Ninja" - cmake --build build/Debug --parallel $(nproc) + conan install . --remote=bigfootpackages -pr:h=./ConanProfiles/host/clang_coverage -pr:b=./ConanProfiles/build/clang --build=missing -s build_type=Debug -o bigfoot/*:build_tests=True -o bigfoot/*:build_benchmarks=True -o bigfoot/*:tracy=False -o bigfoot/*:toolmode=True -o bigfoot/*:vulkan=True -of build-toolmode + . ./build-toolmode/build/Debug/generators/conanbuild.sh + cmake -S . -B ./build-toolmode/Debug --toolchain ./build-toolmode/build/Debug/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -G "Ninja" + cmake --build build-toolmode/Debug --parallel $(nproc) + . ./build-toolmode/build/Debug/generators/deactivate_conanbuild.sh + + - name: Run tests + run: | + cd ./build-toolmode/Debug + . ../../build-toolmode/build/Debug/generators/conanbuild.sh + LLVM_PROFILE_FILE="%p.profraw%c" xvfb-run ctest --output-on-failure + . ../../build-toolmode/build/Debug/generators/deactivate_conanbuild.sh - name: Clang-Tidy - run: run-clang-tidy -p ./build/Debug/ >> tidy_result.txt + run: run-clang-tidy -p ./build-toolmode/Debug/ >> tidy_result.txt - name: Infer - run: infer run --compilation-database build/Debug/compile_commands.json + run: infer run --compilation-database build-toolmode/Debug/compile_commands.json + + - name: Generate coverage report + shell: bash + run: | + # gcovr's own LLVM-JSON reader only understands coverage-mapping format + # 2.x; this image's clang 22 llvm-cov emits format 3.x, which gcovr + # rejects outright ("Wrong major version"). Bypassing gcovr's JSON + # path entirely: llvm-cov's lcov exporter is a separate, unaffected + # code path, so merge -> export lcov -> convert lcov to Cobertura. + llvm-profdata merge -sparse $(find build-toolmode/Debug -name "*.profraw") -o merged.profdata + BINARIES=($(find build-toolmode/Debug -maxdepth 8 -type f -executable -iname "*Tests")) + OBJECT_ARGS=$(printf -- '-object %s ' "${BINARIES[@]:1}") + llvm-cov export -format=lcov -instr-profile=merged.profdata "${BINARIES[0]}" $OBJECT_ARGS > coverage.lcov + # lcov_cobertura isn't preinstalled on the image; inject it into + # gcovr's existing pipx venv and expose its CLI on PATH. + pipx inject gcovr lcov_cobertura --include-apps + lcov_cobertura coverage.lcov -b . -o coverage.xml - name: SonarQube Scan - if: github.head_ref == 'main' || github.ref_name == 'main' uses: SonarSource/sonarqube-scan-action@v7.0.0 with: args: > - -Dsonar.cxx.jsonCompilationDatabase=./build/Debug/compile_commands.json - -Dsonar.verbose=true + -Dsonar.cxx.jsonCompilationDatabase=./build-toolmode/Debug/compile_commands.json + -Dsonar.qualitygate.wait=true + ${{ github.event_name == 'pull_request' && + format('-Dsonar.pullrequest.key={0} -Dsonar.pullrequest.branch={1} -Dsonar.pullrequest.base={2} -Dsonar.analysis.sqbot={3}', + github.event.pull_request.number, github.head_ref, github.base_ref, github.event.pull_request.head.sha) + || format('-Dsonar.branch.name={0}', github.ref_name) }} env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST }} \ No newline at end of file + SONAR_HOST_URL: ${{ secrets.SONAR_HOST }} diff --git a/.gitignore b/.gitignore index 129a851..7e16e03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ [Bb]uild* +!ConanProfiles/build/ +!ConanProfiles/build/** Environment/output *.generated.h @@ -18,4 +20,4 @@ graph_info.json test_package/Vendor -graphviz \ No newline at end of file +graphviz* \ No newline at end of file diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAsset.cpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAsset.cpp new file mode 100644 index 0000000..81cbe04 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAsset.cpp @@ -0,0 +1,11 @@ +/********************************************************************* + * \file BenchmarkAsset.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +BIGFOOT_ASSET(BenchmarkAsset, ::Bigfoot::BenchmarkAsset) diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.cpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.cpp new file mode 100644 index 0000000..dc595c0 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.cpp @@ -0,0 +1,11 @@ +/********************************************************************* + * \file BenchmarkAssetReferred.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +BIGFOOT_ASSET(BenchmarkAssetReferred, ::Bigfoot::BenchmarkAssetReferred) diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/CMakeLists.txt b/Bigfoot/Benchmarks/AssetsForBenchmarking/CMakeLists.txt new file mode 100644 index 0000000..950fef7 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/CMakeLists.txt @@ -0,0 +1,17 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}) + +set(PublicDependencies) +set(PrivateDependencies) +set(BigfootPublicDependencies) +set(BigfootPrivateDependencies + Engine) + +bigfoot_create_package_lib( + "${PublicDependencies}" + "${PrivateDependencies}" + "${BigfootPublicDependencies}" + "${BigfootPrivateDependencies}" + "") + +set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Benchmarks/Bigfoot) diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.fbs b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.fbs new file mode 100644 index 0000000..a022054 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.fbs @@ -0,0 +1,11 @@ +include "Engine/Asset/Reference.fbs"; + +namespace Flat.Bigfoot; + +table BenchmarkAsset +{ + payload: [ubyte]; + refs: [HardReference] (native_inline, bigfoot_ref: "::Bigfoot::BenchmarkAssetReferred"); +} + +root_type BenchmarkAsset; diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.hpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.hpp new file mode 100644 index 0000000..2f4fa48 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset.hpp @@ -0,0 +1,25 @@ +/********************************************************************* + * \file BenchmarkAsset.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ASSETSFORBENCHMARKING_ASSET_BENCHMARKASSET_HPP +#define BIGFOOT_ASSETSFORBENCHMARKING_ASSET_BENCHMARKASSET_HPP +#include + +#include + +namespace Bigfoot +{ +struct BenchmarkAssetTraits +{ + using FLAT = ::Flat::Bigfoot::BenchmarkAsset; +}; + +class BenchmarkAsset: public Asset +{ +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.fbs b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.fbs new file mode 100644 index 0000000..5b43973 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.fbs @@ -0,0 +1,7 @@ +namespace Flat.Bigfoot; + +table BenchmarkAssetReferred +{ +} + +root_type BenchmarkAssetReferred; diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp new file mode 100644 index 0000000..f306e30 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp @@ -0,0 +1,25 @@ +/********************************************************************* + * \file BenchmarkAssetReferred.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ASSETSFORBENCHMARKING_ASSET_BENCHMARKASSETREFERRED_HPP +#define BIGFOOT_ASSETSFORBENCHMARKING_ASSET_BENCHMARKASSETREFERRED_HPP +#include + +#include + +namespace Bigfoot +{ +struct BenchmarkAssetReferredTraits +{ + using FLAT = ::Flat::Bigfoot::BenchmarkAssetReferred; +}; + +class BenchmarkAssetReferred: public Asset +{ +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred_generated.hpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred_generated.hpp new file mode 100644 index 0000000..bbd8dd2 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAssetReferred_generated.hpp @@ -0,0 +1,172 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_BENCHMARKASSETREFERRED_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_BENCHMARKASSETREFERRED_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct BenchmarkAssetReferred; +struct BenchmarkAssetReferredBuilder; +struct BenchmarkAssetReferredT; + +inline const ::flatbuffers::TypeTable *BenchmarkAssetReferredTypeTable(); + +struct BenchmarkAssetReferredT : public ::flatbuffers::NativeTable { + typedef BenchmarkAssetReferred TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.BenchmarkAssetReferredT"; + } +}; + +struct BenchmarkAssetReferred FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef BenchmarkAssetReferredT NativeTableType; + typedef BenchmarkAssetReferredBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return BenchmarkAssetReferredTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.BenchmarkAssetReferred"; + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + verifier.EndTable(); + } + BenchmarkAssetReferredT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(BenchmarkAssetReferredT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetReferredT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct BenchmarkAssetReferredBuilder { + typedef BenchmarkAssetReferred Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + explicit BenchmarkAssetReferredBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateBenchmarkAssetReferred( + ::flatbuffers::FlatBufferBuilder &_fbb) { + BenchmarkAssetReferredBuilder builder_(_fbb); + return builder_.Finish(); +} + +struct BenchmarkAssetReferred::Traits { + using type = BenchmarkAssetReferred; + static auto constexpr Create = CreateBenchmarkAssetReferred; +}; + +::flatbuffers::Offset CreateBenchmarkAssetReferred(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetReferredT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline BenchmarkAssetReferredT *BenchmarkAssetReferred::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void BenchmarkAssetReferred::UnPackTo(BenchmarkAssetReferredT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; +} + +inline ::flatbuffers::Offset CreateBenchmarkAssetReferred(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetReferredT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return BenchmarkAssetReferred::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset BenchmarkAssetReferred::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetReferredT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const BenchmarkAssetReferredT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + return Flat::Bigfoot::CreateBenchmarkAssetReferred( + _fbb); +} + +inline const ::flatbuffers::TypeTable *BenchmarkAssetReferredTypeTable() { + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 0, nullptr, nullptr, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::BenchmarkAssetReferred *GetBenchmarkAssetReferred(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::BenchmarkAssetReferred *GetSizePrefixedBenchmarkAssetReferred(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyBenchmarkAssetReferredBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedBenchmarkAssetReferredBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *BenchmarkAssetReferredExtension() { + return "bfbs"; +} + +inline void FinishBenchmarkAssetReferredBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedBenchmarkAssetReferredBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackBenchmarkAssetReferred( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetBenchmarkAssetReferred(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedBenchmarkAssetReferred( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedBenchmarkAssetReferred(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +#endif // FLATBUFFERS_GENERATED_BENCHMARKASSETREFERRED_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset_generated.hpp b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset_generated.hpp new file mode 100644 index 0000000..b816755 --- /dev/null +++ b/Bigfoot/Benchmarks/AssetsForBenchmarking/Include/AssetsForBenchmarking/Asset/BenchmarkAsset_generated.hpp @@ -0,0 +1,238 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_BENCHMARKASSET_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_BENCHMARKASSET_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/Reference.hpp" +#include "Engine/Asset/Reference_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct BenchmarkAsset; +struct BenchmarkAssetBuilder; +struct BenchmarkAssetT; + +} // namespace Bigfoot +} // namespace Flat + +namespace Bigfoot { +class BenchmarkAssetReferred; +} // namespace Bigfoot + +namespace flatbuffers { +Flat::Bigfoot::HardReference PackHardReferenceBenchmarkAssetReferred(const ::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred>& p_asset); +::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred> UnPackHardReferenceBenchmarkAssetReferred(const Flat::Bigfoot::HardReference& p_asset); +} // namespace flatbuffers + +namespace Flat { +namespace Bigfoot { + +inline const ::flatbuffers::TypeTable *BenchmarkAssetTypeTable(); + +struct BenchmarkAssetT : public ::flatbuffers::NativeTable { + typedef BenchmarkAsset TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.BenchmarkAssetT"; + } + eastl::vector payload{}; + eastl::vector<::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred>> refs{}; +}; + +struct BenchmarkAsset FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef BenchmarkAssetT NativeTableType; + typedef BenchmarkAssetBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return BenchmarkAssetTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.BenchmarkAsset"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_PAYLOAD = 4, + VT_REFS = 6 + }; + const ::flatbuffers::Vector *payload() const { + return GetPointer *>(VT_PAYLOAD); + } + const ::flatbuffers::Vector *refs() const { + return GetPointer *>(VT_REFS); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_PAYLOAD) && + verifier.VerifyVector(payload()) && + VerifyOffset(verifier, VT_REFS) && + verifier.VerifyVector(refs()) && + verifier.EndTable(); + } + BenchmarkAssetT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(BenchmarkAssetT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct BenchmarkAssetBuilder { + typedef BenchmarkAsset Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_payload(::flatbuffers::Offset<::flatbuffers::Vector> payload) { + fbb_.AddOffset(BenchmarkAsset::VT_PAYLOAD, payload); + } + void add_refs(::flatbuffers::Offset<::flatbuffers::Vector> refs) { + fbb_.AddOffset(BenchmarkAsset::VT_REFS, refs); + } + explicit BenchmarkAssetBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateBenchmarkAsset( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset<::flatbuffers::Vector> payload = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> refs = 0) { + BenchmarkAssetBuilder builder_(_fbb); + builder_.add_refs(refs); + builder_.add_payload(payload); + return builder_.Finish(); +} + +struct BenchmarkAsset::Traits { + using type = BenchmarkAsset; + static auto constexpr Create = CreateBenchmarkAsset; +}; + +::flatbuffers::Offset CreateBenchmarkAsset(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline BenchmarkAssetT *BenchmarkAsset::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void BenchmarkAsset::UnPackTo(BenchmarkAssetT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = payload(); if (_e) { _o->payload.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->payload.begin()); } } + { auto _e = refs(); if (_e) { _o->refs.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->refs[_i] = ::flatbuffers::UnPackHardReferenceBenchmarkAssetReferred(*_e->Get(_i)); } } else { _o->refs.resize(0); } } +} + +inline ::flatbuffers::Offset CreateBenchmarkAsset(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return BenchmarkAsset::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset BenchmarkAsset::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const BenchmarkAssetT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _payload = _fbb.CreateVector(_o->payload.data(), _o->payload.size()); + auto _refs = _fbb.CreateVectorOfNativeStructs>(_o->refs.data(), _o->refs.size(), ::flatbuffers::PackHardReferenceBenchmarkAssetReferred); + return Flat::Bigfoot::CreateBenchmarkAsset( + _fbb, + _payload, + _refs); +} + +inline const ::flatbuffers::TypeTable *BenchmarkAssetTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_UCHAR, 1, -1 }, + { ::flatbuffers::ET_SEQUENCE, 1, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::HardReferenceTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 2, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::BenchmarkAsset *GetBenchmarkAsset(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::BenchmarkAsset *GetSizePrefixedBenchmarkAsset(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyBenchmarkAssetBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedBenchmarkAssetBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *BenchmarkAssetExtension() { + return "bfbs"; +} + +inline void FinishBenchmarkAssetBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedBenchmarkAssetBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackBenchmarkAsset( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetBenchmarkAsset(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedBenchmarkAsset( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedBenchmarkAsset(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +namespace flatbuffers { +#ifndef FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEBENCHMARKASSETREFERRED +#define FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEBENCHMARKASSETREFERRED +inline Flat::Bigfoot::HardReference PackHardReferenceBenchmarkAssetReferred(const ::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred> UnPackHardReferenceBenchmarkAssetReferred(const Flat::Bigfoot::HardReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEBENCHMARKASSETREFERRED +} // namespace flatbuffers + +#endif // FLATBUFFERS_GENERATED_BENCHMARKASSET_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Benchmarks/CMakeLists.txt b/Bigfoot/Benchmarks/CMakeLists.txt new file mode 100644 index 0000000..6219887 --- /dev/null +++ b/Bigfoot/Benchmarks/CMakeLists.txt @@ -0,0 +1,7 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/AssetsForBenchmarking) + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Utils) +if(${TOOLMODE}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Tools) +endif() diff --git a/Bigfoot/Benchmarks/Engine/Asset/AssetContainer.cpp b/Bigfoot/Benchmarks/Engine/Asset/AssetContainer.cpp new file mode 100644 index 0000000..1e7bfae --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Asset/AssetContainer.cpp @@ -0,0 +1,175 @@ +/********************************************************************* + * \file AssetContainer.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include +#include + +#include + +namespace Bigfoot +{ +namespace +{ +using Random = effolkronium::random_static; +} // namespace + +class AssetContainerFixture: public benchmark::Fixture +{ + protected: + [[nodiscard]] + static eastl::vector InsertRandomAssets(AssetContainer& p_container, + const std::uint32_t p_count) + { + eastl::vector uuids; + uuids.reserve(p_count); + for (std::uint32_t i = 0; i < p_count; ++i) + { + uuids.push_back(p_container.Insert()->GetAsset()->GetHeader().uuid); + } + Random::shuffle(uuids); + + return uuids; + } + + AssetContainer m_container; +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(AssetContainerFixture, Insert)(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + for (auto _: state) + { + AssetContainer container; + for (std::uint32_t i = 0; i < count; ++i) + { + benchmark::DoNotOptimize(container.Insert()); + } + benchmark::DoNotOptimize(container); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(BenchmarkAsset))); +} + +BENCHMARK_REGISTER_F(AssetContainerFixture, Insert)->Name("AssetContainer/Insert")->Arg(512)->Arg(8192)->Arg(131'072); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(AssetContainerFixture, Get)(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + const eastl::vector uuids = InsertRandomAssets(m_container, count); + + eastl::vector handles; + handles.reserve(count); + for (const UUID& uuid: uuids) + { + handles.push_back(m_container.AcquireContainerHandle(uuid)); + } + + for (auto _: state) + { + std::uint64_t sum = 0; + for (ContainerHandle* const handle: handles) + { + if (const auto* const asset = static_cast(handle->GetAsset())) + { + sum += asset->GetHeader().type_id; + } + } + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(state.iterations() * count); +} + +BENCHMARK_REGISTER_F(AssetContainerFixture, Get)->Name("AssetContainer/Get")->Arg(512)->Arg(8192)->Arg(131'072); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(AssetContainerFixture, Has)(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + const eastl::vector uuids = InsertRandomAssets(m_container, count); + + for (auto _: state) + { + bool result = true; + for (const UUID& uuid: uuids) + { + result = result && m_container.Has(uuid); + } + benchmark::DoNotOptimize(result); + } + + state.SetItemsProcessed(state.iterations() * count); +} + +BENCHMARK_REGISTER_F(AssetContainerFixture, Has)->Name("AssetContainer/Has")->Arg(512)->Arg(8192)->Arg(131'072); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(AssetContainerFixture, AcquireContainerHandle)(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + const eastl::vector uuids = InsertRandomAssets(m_container, count); + + for (auto _: state) + { + std::uint64_t sum = 0; + for (const UUID& uuid: uuids) + { + sum += m_container.AcquireContainerHandle(uuid)->GetHardRefCount(); + } + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(state.iterations() * count); +} + +BENCHMARK_REGISTER_F(AssetContainerFixture, AcquireContainerHandle) + ->Name("AssetContainer/AcquireContainerHandle") + ->Arg(512) + ->Arg(8192) + ->Arg(131'072); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(AssetContainerFixture, Remove)(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + eastl::vector uuids; + + for (auto _: state) + { + state.PauseTiming(); + uuids = InsertRandomAssets(m_container, count); + state.ResumeTiming(); + + for (const UUID& uuid: uuids) + { + m_container.Remove(uuid); + } + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(BenchmarkAsset))); +} + +BENCHMARK_REGISTER_F(AssetContainerFixture, Remove)->Name("AssetContainer/Remove")->Arg(512)->Arg(8192)->Arg(131'072); +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Engine/Asset/Reference.cpp b/Bigfoot/Benchmarks/Engine/Asset/Reference.cpp new file mode 100644 index 0000000..6086e51 --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Asset/Reference.cpp @@ -0,0 +1,187 @@ +/********************************************************************* + * \file Reference.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +namespace Bigfoot +{ +template class REFERENCE> +class ReferenceFixture: public benchmark::Fixture +{ + protected: + using ReferenceType = REFERENCE; + + void SetUp(benchmark::State&) override + { + SetActiveAssetLibrary(&m_library); + std::ignore = m_library.Insert(&m_container); + + m_uuid = CreateAsset()->GetHeader().uuid; + m_reference.emplace(m_uuid); + } + + void TearDown(benchmark::State&) override + { + SetActiveAssetLibrary(&g_assetLibrary); + } + + AssetLibrary m_library; + AssetContainer m_container; + UUID m_uuid; + eastl::optional m_reference; +}; + +/****************************************************************************************/ + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, HardReferenceConstruct, HardReference)(benchmark::State& state) +{ + for (auto _: state) + { + ReferenceType reference {m_uuid}; + benchmark::DoNotOptimize(reference); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, HardReferenceConstruct)->Name("Reference/HardReference/Construct"); + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, SoftReferenceConstruct, SoftReference)(benchmark::State& state) +{ + for (auto _: state) + { + ReferenceType reference {m_uuid}; + benchmark::DoNotOptimize(reference); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, SoftReferenceConstruct)->Name("Reference/SoftReference/Construct"); + +/****************************************************************************************/ + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, HardReferenceGet, HardReference)(benchmark::State& state) +{ + for (auto _: state) + { + benchmark::DoNotOptimize(m_reference->Get()); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, HardReferenceGet)->Name("Reference/HardReference/Get"); + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, SoftReferenceGet, SoftReference)(benchmark::State& state) +{ + for (auto _: state) + { + benchmark::DoNotOptimize(m_reference->Get()); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, SoftReferenceGet)->Name("Reference/SoftReference/Get"); + +/****************************************************************************************/ + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, HardReferenceCopy, HardReference)(benchmark::State& state) +{ + for (auto _: state) + { + ReferenceType copy {*m_reference}; + benchmark::DoNotOptimize(copy); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, HardReferenceCopy)->Name("Reference/HardReference/Copy"); + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, SoftReferenceCopy, SoftReference)(benchmark::State& state) +{ + for (auto _: state) + { + ReferenceType copy {*m_reference}; + benchmark::DoNotOptimize(copy); + } + + state.SetItemsProcessed(state.iterations()); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, SoftReferenceCopy)->Name("Reference/SoftReference/Copy"); + +/****************************************************************************************/ + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, HardReferenceMove, HardReference)(benchmark::State& state) +{ + constexpr std::size_t BATCH_SIZE = 128; + eastl::vector batch; + batch.reserve(BATCH_SIZE); + + for (auto _: state) + { + state.PauseTiming(); + batch.clear(); + for (std::size_t i = 0; i < BATCH_SIZE; ++i) + { + batch.emplace_back(m_uuid); + } + state.ResumeTiming(); + + for (ReferenceType& reference: batch) + { + ReferenceType moved {std::move(reference)}; + benchmark::DoNotOptimize(moved); + } + } + + state.SetItemsProcessed(state.iterations() * BATCH_SIZE); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, HardReferenceMove)->Name("Reference/HardReference/Move"); + +BENCHMARK_TEMPLATE_DEFINE_F(ReferenceFixture, SoftReferenceMove, SoftReference)(benchmark::State& state) +{ + constexpr std::size_t BATCH_SIZE = 128; + eastl::vector batch; + batch.reserve(BATCH_SIZE); + + for (auto _: state) + { + state.PauseTiming(); + batch.clear(); + for (std::size_t i = 0; i < BATCH_SIZE; ++i) + { + batch.emplace_back(m_uuid); + } + state.ResumeTiming(); + + for (ReferenceType& reference: batch) + { + ReferenceType moved {std::move(reference)}; + benchmark::DoNotOptimize(moved); + } + } + + state.SetItemsProcessed(state.iterations() * BATCH_SIZE); +} + +BENCHMARK_REGISTER_F(ReferenceFixture, SoftReferenceMove)->Name("Reference/SoftReference/Move"); +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp b/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp new file mode 100644 index 0000000..db46a6f --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/BigFile/BigFile.cpp @@ -0,0 +1,312 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_TOOLMODE +using Random = effolkronium::random_static; + +class BigFileFixture: public benchmark::Fixture +{ + protected: + static constexpr std::uint32_t ASSET_COUNT = 20'000; + static constexpr std::uint32_t REFS_PER_ASSET = 4; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + + void SetUp([[maybe_unused]] benchmark::State& p_state) override + { + // The DB is populated exactly once for the entire benchmark process. + // SetUp is called before every calibration round (Google Benchmark doubles + // iteration counts until it reaches a stable measurement), so rebuilding + // the DB each time would dominate the total benchmark time. + m_uuids = BuildData(); + m_bigFile.emplace(File {BIGFILE_ENGINEBENCHMARKS_LOCATION}); + } + + void TearDown([[maybe_unused]] benchmark::State& p_state) override + { + m_bigFile.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_bigFile; + eastl::span m_uuids; + + private: + static const eastl::vector& BuildData() + { + static const eastl::vector s_uuids = []() + { + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + uuids.reserve(ASSET_COUNT); + flatbuffers.reserve(ASSET_COUNT); + hardReferences.reserve(ASSET_COUNT); + + for (std::uint32_t i = 0; i < ASSET_COUNT; ++i) + { + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ASSET; ++j) + { + BenchmarkAssetReferred assetReferred; + assetReferred.SetName(assetReferred.GetHeader().uuid.ToString()); + + asset.GetAsset().refs.emplace_back(assetReferred.GetHeader().uuid); + } + + eastl::vector flatbuffer = asset.Pack(); + hardReferences.push_back(asset.GetHardReferences(flatbuffer)); + flatbuffers.push_back(std::move(flatbuffer)); + uuids.push_back(asset.GetHeader().uuid); + } + + BigFile bigFile {File {BIGFILE_ENGINEBENCHMARKS_LOCATION}}; + bigFile.Clear(); + bigFile.SaveAssetFlatbuffers(uuids, flatbuffers, hardReferences); + + return uuids; + }(); + + return s_uuids; + } +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetFlatbuffer)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + eastl::vector flatbuffer = m_bigFile->GetAssetFlatbuffer( + m_uuids[Random::get(0, m_uuids.size() - 1)]); + benchmark::DoNotOptimize(flatbuffer); + + bytesProcessed += static_cast(flatbuffer.size()); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetFlatbuffer)->Name("BigFile/GetAssetFlatbuffer")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, 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_bigFile->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(BigFileFixture, GetAssetFlatbuffers) + ->Name("BigFile/GetAssetFlatbuffers") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096) + ->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetHardReferences)(benchmark::State& state) +{ + for (auto _: state) + { + benchmark::DoNotOptimize( + m_bigFile->GetAssetHardReferences(m_uuids[Random::get(0, m_uuids.size() - 1)])); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(static_cast(state.iterations()) * REFS_PER_ASSET * UUID::UUID_BYTE_SIZE); +} + +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetHardReferences)->Name("BigFile/GetAssetHardReferences")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, GetAssetsHardReferences)(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::size_t batchIndex = 0; + for (auto _: state) + { + benchmark::DoNotOptimize(m_bigFile->GetAssetsHardReferences(batchPool[batchIndex])); + batchIndex = (batchIndex + 1) % BATCH_POOL_SIZE; + } + + state.SetItemsProcessed(state.iterations() * batchSize); + state.SetBytesProcessed(static_cast(state.iterations()) * batchSize * REFS_PER_ASSET * + UUID::UUID_BYTE_SIZE); +} + +BENCHMARK_REGISTER_F(BigFileFixture, GetAssetsHardReferences) + ->Name("BigFile/GetAssetsHardReferences") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096) + ->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, SaveAssetFlatbuffer)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ASSET; ++j) + { + asset.GetAsset().refs.emplace_back(m_uuids[Random::get(0, m_uuids.size() - 1)]); + } + + const eastl::vector flatbuffer = asset.Pack(); + const eastl::vector hardReferences = asset.GetHardReferences(flatbuffer); + bytesProcessed += static_cast(flatbuffer.size()); + state.ResumeTiming(); + + m_bigFile->SaveAssetFlatbuffer(asset.GetHeader().uuid, flatbuffer, hardReferences); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(BigFileFixture, SaveAssetFlatbuffer)->Name("BigFile/SaveAssetFlatbuffer")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(BigFileFixture, SaveAssetFlatbuffers)(benchmark::State& state) +{ + const auto batchSize = static_cast(state.range(0)); + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + uuids.reserve(batchSize); + flatbuffers.reserve(batchSize); + hardReferences.reserve(batchSize); + + for (std::size_t i = 0; i < batchSize; ++i) + { + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ASSET; ++j) + { + asset.GetAsset().refs.emplace_back(m_uuids[Random::get(0, m_uuids.size() - 1)]); + } + + eastl::vector flatbuffer = asset.Pack(); + uuids.push_back(asset.GetHeader().uuid); + bytesProcessed += static_cast(flatbuffer.size()); + hardReferences.push_back(asset.GetHardReferences(flatbuffer)); + flatbuffers.push_back(std::move(flatbuffer)); + } + state.ResumeTiming(); + + m_bigFile->SaveAssetFlatbuffers(uuids, flatbuffers, hardReferences); + } + + state.SetItemsProcessed(state.iterations() * batchSize); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(BigFileFixture, SaveAssetFlatbuffers) + ->Name("BigFile/SaveAssetFlatbuffers") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096) + ->UseRealTime(); +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Engine/CMakeLists.txt b/Bigfoot/Benchmarks/Engine/CMakeLists.txt new file mode 100644 index 0000000..0bc14d1 --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/CMakeLists.txt @@ -0,0 +1,14 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}Benchmarks) + +bigfoot_create_package_benchmarks( + "") + +target_link_libraries(${PROJECT_NAME} + PRIVATE + effolkronium_random + $) + +bigfoot_create_bigfile("Benchmarks/Bigfoot") + +bigfoot_create_sasquatch_info() diff --git a/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/BigFileInfo_generated.hpp b/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/BigFileInfo_generated.hpp new file mode 100644 index 0000000..e0599ce --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Include/EngineBenchmarks/BigFileInfo_generated.hpp @@ -0,0 +1,22 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file BigFileInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_ENGINEBENCHMARKS_BIGFILEINFO_GENERATED_HPP +#define BIGFOOT_ENGINEBENCHMARKS_BIGFILEINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_TOOLMODE + +namespace Bigfoot +{ +/* + * BigFile location + */ +constexpr eastl::string_view BIGFILE_ENGINEBENCHMARKS_LOCATION {"./EngineBenchmarks.db"}; +} // namespace Bigfoot +#endif +#endif 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..e24d963 --- /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"}; +} // 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..ec5be5e --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,245 @@ +/********************************************************************* + * \file Sasquatch.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#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::uint32_t REFS_PER_ASSET = 4; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + + void SetUp([[maybe_unused]] benchmark::State& p_state) override + { + // The Sasquatch file is built exactly once for the entire benchmark process. + // SetUp is called before every calibration round (Google Benchmark doubles + // iteration counts until it reaches a stable measurement), so rebuilding + // the file each time would dominate the total benchmark time. + m_uuids = BuildData(); + m_sasquatch.emplace(File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}); + } + + void TearDown([[maybe_unused]] benchmark::State& p_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::span m_uuids; + + private: + static const eastl::vector& BuildData() + { + static const eastl::vector s_uuids = []() + { + File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}.Delete(); + + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + uuids.reserve(ASSET_COUNT); + flatbuffers.reserve(ASSET_COUNT); + hardReferences.reserve(ASSET_COUNT); + + for (std::uint32_t i = 0; i < ASSET_COUNT; ++i) + { + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ASSET; ++j) + { + BenchmarkAssetReferred assetReferred; + assetReferred.SetName(assetReferred.GetHeader().uuid.ToString()); + + asset.GetAsset().refs.emplace_back(assetReferred.GetHeader().uuid); + } + + eastl::vector flatbuffer = asset.Pack(); + hardReferences.push_back(asset.GetHardReferences(flatbuffer)); + flatbuffers.push_back(std::move(flatbuffer)); + uuids.push_back(asset.GetHeader().uuid); + } + + std::size_t totalFlatbufferBytes = 0; + std::size_t totalHardReferencesBytes = 0; + for (std::size_t i = 0; i < uuids.size(); ++i) + { + totalFlatbufferBytes += flatbuffers[i].size(); + totalHardReferencesBytes += hardReferences[i].size() * UUID::UUID_BYTE_SIZE; + } + + SasquatchWriter writer {File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}, + uuids.size(), + totalFlatbufferBytes, + totalHardReferencesBytes}; + for (std::size_t i = 0; i < uuids.size(); ++i) + { + writer.WriteAsset(uuids[i], flatbuffers[i], hardReferences[i]); + } + + return uuids; + }(); + + return s_uuids; + } +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchFixture, GetAssetHardReferences)(benchmark::State& state) +{ + for (auto _: state) + { + benchmark::DoNotOptimize( + m_sasquatch->GetAssetHardReferences(m_uuids[Random::get(0, m_uuids.size() - 1)])); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(static_cast(state.iterations()) * REFS_PER_ASSET * UUID::UUID_BYTE_SIZE); +} + +BENCHMARK_REGISTER_F(SasquatchFixture, GetAssetHardReferences)->Name("Sasquatch/GetAssetHardReferences")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchFixture, GetAssetsHardReferences)(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::size_t batchIndex = 0; + for (auto _: state) + { + benchmark::DoNotOptimize(m_sasquatch->GetAssetsHardReferences(batchPool[batchIndex])); + batchIndex = (batchIndex + 1) % BATCH_POOL_SIZE; + } + + state.SetItemsProcessed(state.iterations() * batchSize); + state.SetBytesProcessed(static_cast(state.iterations()) * batchSize * REFS_PER_ASSET * + UUID::UUID_BYTE_SIZE); +} + +BENCHMARK_REGISTER_F(SasquatchFixture, GetAssetsHardReferences) + ->Name("Sasquatch/GetAssetsHardReferences") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096) + ->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchFixture, GetAssetFlatbuffer)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + eastl::vector flatbuffer = m_sasquatch->GetAssetFlatbuffer( + m_uuids[Random::get(0, m_uuids.size() - 1)]); + benchmark::DoNotOptimize(flatbuffer); + + bytesProcessed += static_cast(flatbuffer.size()); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SasquatchFixture, GetAssetFlatbuffer)->Name("Sasquatch/GetAssetFlatbuffer")->UseRealTime(); + +/****************************************************************************************/ + +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) + ->UseRealTime(); +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Engine/SerializationManager/SerializationManager.cpp b/Bigfoot/Benchmarks/Engine/SerializationManager/SerializationManager.cpp new file mode 100644 index 0000000..93541c6 --- /dev/null +++ b/Bigfoot/Benchmarks/Engine/SerializationManager/SerializationManager.cpp @@ -0,0 +1,391 @@ +/********************************************************************* + * \file SerializationManager.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#if defined BIGFOOT_TOOLMODE +#include + +#include +#elif defined BIGFOOT_GAME +#include + +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Bigfoot +{ +using Random = effolkronium::random_static; + +class SerializationManagerFixture: public benchmark::Fixture +{ + protected: + static constexpr std::uint32_t ROOT_ASSET_COUNT = 20'000; + static constexpr std::uint32_t REFS_PER_ROOT = 4; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + + void SetUp([[maybe_unused]] benchmark::State& p_state) override + { + m_previousDataAccessor = g_dataAccessor; + m_previousAssetLibrary = g_activeAssetLibrary; + + // The data store is populated exactly once for the entire benchmark process. + // SetUp is called before every calibration round (Google Benchmark doubles + // iteration counts until it reaches a stable measurement), so rebuilding + // the data store each time would dominate the total benchmark time. + static const SharedData& s_data = BuildData(); + m_rootUuids = s_data.rootUuids; + m_leafUuids = s_data.leafUuids; + m_rootTotalBytes = &s_data.rootTotalBytes; + +#if defined BIGFOOT_TOOLMODE + m_bigFile.emplace(File {BIGFILE_ENGINEBENCHMARKS_LOCATION}); + SetDataAccessor(&*m_bigFile); +#elif defined BIGFOOT_GAME + m_sasquatch.emplace(File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}); + SetDataAccessor(&*m_sasquatch); +#endif + } + + void TearDown([[maybe_unused]] benchmark::State& p_state) override + { +#if defined BIGFOOT_TOOLMODE + m_bigFile.reset(); +#elif defined BIGFOOT_GAME + m_sasquatch.reset(); +#endif + SetDataAccessor(m_previousDataAccessor); + SetActiveAssetLibrary(m_previousAssetLibrary); + } + + /* + * Registers a fresh, empty AssetLibrary as the active one, so that every root and leaf asset appears unloaded + * + * \param p_library The library to register + * \param p_leafContainer The container to register leaves into + * \param p_rootContainer The container to register roots into + */ + static void ResetActiveLibrary(AssetLibrary& p_library, + AssetContainer& p_leafContainer, + AssetContainer& p_rootContainer) + { + std::ignore = p_library.Insert(&p_leafContainer); + std::ignore = p_library.Insert(&p_rootContainer); + SetActiveAssetLibrary(&p_library); + } + + [[nodiscard]] + eastl::vector RandomRootBatch(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_rootUuids[Random::get(0, m_rootUuids.size() - 1)]); + } + + return batch; + } + + /* + * Total flatbuffer bytes (root + its leaves) that loading p_roots will actually read, deduplicated the same way + * SerializationManager::LoadAssets deduplicates its own transitive discovery + * + * \param p_roots The root assets that would be loaded + * \return The total flatbuffer bytes across the unique roots + */ + [[nodiscard]] + std::int64_t TotalBytes(const eastl::vector& p_roots) const + { + ankerl::unordered_dense::set unique; + unique.reserve(p_roots.size()); + for (const UUID& uuid: p_roots) + { + unique.insert(uuid); + } + + std::int64_t bytes = 0; + for (const UUID& uuid: unique) + { + bytes += static_cast(m_rootTotalBytes->at(uuid)); + } + + return bytes; + } + + DataAccessor* m_previousDataAccessor = nullptr; + AssetLibrary* m_previousAssetLibrary = nullptr; + + eastl::span m_rootUuids; + eastl::span m_leafUuids; + const ankerl::unordered_dense::map* m_rootTotalBytes = nullptr; + +#if defined BIGFOOT_TOOLMODE + eastl::optional m_bigFile; +#elif defined BIGFOOT_GAME + eastl::optional m_sasquatch; +#endif + + private: + struct SharedData + { + eastl::vector rootUuids; + eastl::vector leafUuids; + ankerl::unordered_dense::map rootTotalBytes; + }; + + static const SharedData& BuildData() + { + static const SharedData s_data = []() + { + SharedData data; + data.rootUuids.reserve(ROOT_ASSET_COUNT); + data.leafUuids.reserve(ROOT_ASSET_COUNT * REFS_PER_ROOT); + data.rootTotalBytes.reserve(ROOT_ASSET_COUNT); + + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + uuids.reserve(ROOT_ASSET_COUNT * (1 + REFS_PER_ROOT)); + flatbuffers.reserve(uuids.capacity()); + hardReferences.reserve(uuids.capacity()); + + for (std::uint32_t i = 0; i < ROOT_ASSET_COUNT; ++i) + { + BenchmarkAsset root; + root.SetName(root.GetHeader().uuid.ToString()); + root.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + + std::size_t totalBytes = 0; + for (std::uint32_t j = 0; j < REFS_PER_ROOT; ++j) + { + BenchmarkAssetReferred leaf; + leaf.SetName(leaf.GetHeader().uuid.ToString()); + + root.GetAsset().refs.emplace_back(leaf.GetHeader().uuid); + + eastl::vector leafFlatbuffer = leaf.Pack(); + totalBytes += leafFlatbuffer.size(); + data.leafUuids.push_back(leaf.GetHeader().uuid); + uuids.push_back(leaf.GetHeader().uuid); + hardReferences.push_back(leaf.GetHardReferences(leafFlatbuffer)); + flatbuffers.push_back(std::move(leafFlatbuffer)); + } + + eastl::vector rootFlatbuffer = root.Pack(); + totalBytes += rootFlatbuffer.size(); + data.rootTotalBytes.emplace(root.GetHeader().uuid, totalBytes); + data.rootUuids.push_back(root.GetHeader().uuid); + uuids.push_back(root.GetHeader().uuid); + hardReferences.push_back(root.GetHardReferences(rootFlatbuffer)); + flatbuffers.push_back(std::move(rootFlatbuffer)); + } + +#if defined BIGFOOT_TOOLMODE + BigFile bigFile {File { BIGFILE_ENGINEBENCHMARKS_LOCATION }}; + bigFile.Clear(); + bigFile.SaveAssetFlatbuffers(uuids, flatbuffers, hardReferences); +#elif defined BIGFOOT_GAME + std::size_t totalFlatbufferBytes = 0; + std::size_t totalHardReferencesBytes = 0; + for (std::size_t i = 0; i < uuids.size(); ++i) + { + totalFlatbufferBytes += flatbuffers[i].size(); + totalHardReferencesBytes += hardReferences[i].size() * UUID::UUID_BYTE_SIZE; + } + + File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}.Delete(); + SasquatchWriter writer {File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}, + uuids.size(), + totalFlatbufferBytes, + totalHardReferencesBytes}; + for (std::size_t i = 0; i < uuids.size(); ++i) + { + writer.WriteAsset(uuids[i], flatbuffers[i], hardReferences[i]); + } +#endif + + return data; + }(); + + return s_data; + } +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SerializationManagerFixture, LoadAsset)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + AssetLibrary library; + // leafContainer must outlive rootContainer: the loaded root's HardReferences release into leafContainer's + // handles on destruction, so declaring it second would tear it down first and use-after-free those handles. + AssetContainer leafContainer; + AssetContainer rootContainer; + ResetActiveLibrary(library, leafContainer, rootContainer); + + const UUID uuid = m_rootUuids[Random::get(0, m_rootUuids.size() - 1)]; + const std::size_t uuidBytes = m_rootTotalBytes->at(uuid); + state.ResumeTiming(); + + SerializationManager::LoadAsset(uuid); + bytesProcessed += static_cast(uuidBytes); + } + + state.SetItemsProcessed(state.iterations() * (1 + REFS_PER_ROOT)); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SerializationManagerFixture, LoadAsset)->Name("SerializationManager/LoadAsset")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SerializationManagerFixture, LoadAssets)(benchmark::State& state) +{ + const auto batchSize = static_cast(state.range(0)); + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + AssetLibrary library; + // leafContainer must outlive rootContainer, see the same comment in the LoadAsset benchmark above. + AssetContainer leafContainer; + AssetContainer rootContainer; + ResetActiveLibrary(library, leafContainer, rootContainer); + + const eastl::vector batch = RandomRootBatch(batchSize); + const std::int64_t batchBytes = TotalBytes(batch); + state.ResumeTiming(); + + SerializationManager::LoadAssets(batch); + bytesProcessed += batchBytes; + } + + state.SetItemsProcessed(state.iterations() * batchSize * (1 + REFS_PER_ROOT)); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SerializationManagerFixture, LoadAssets) + ->Name("SerializationManager/LoadAssets") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->Arg(4096) + ->UseRealTime(); + +/****************************************************************************************/ + +#if defined BIGFOOT_TOOLMODE +BENCHMARK_DEFINE_F(SerializationManagerFixture, SaveAsset)(benchmark::State& state) +{ + AssetLibrary library; + AssetContainer container; + std::ignore = library.Insert(&container); + SetActiveAssetLibrary(&library); + + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + auto* asset = CreateAsset(); + asset->SetName(asset->GetHeader().uuid.ToString()); + asset->GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ROOT; ++j) + { + asset->GetAsset().refs.emplace_back(m_leafUuids[Random::get(0, m_leafUuids.size() - 1)]); + } + const UUID uuid = asset->GetHeader().uuid; + const std::size_t assetBytes = asset->Pack().size(); + bytesProcessed += static_cast(assetBytes); + state.ResumeTiming(); + + benchmark::DoNotOptimize(SerializationManager::SaveAsset(uuid)); + } + + state.SetItemsProcessed(state.iterations()); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SerializationManagerFixture, SaveAsset)->Name("SerializationManager/SaveAsset")->UseRealTime(); + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SerializationManagerFixture, SaveAssets)(benchmark::State& state) +{ + const auto batchSize = static_cast(state.range(0)); + + AssetLibrary library; + AssetContainer container; + std::ignore = library.Insert(&container); + SetActiveAssetLibrary(&library); + + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + state.PauseTiming(); + eastl::vector uuids; + uuids.reserve(batchSize); + std::size_t batchBytes = 0; + for (std::size_t i = 0; i < batchSize; ++i) + { + auto* asset = CreateAsset(); + asset->SetName(asset->GetHeader().uuid.ToString()); + asset->GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + for (std::uint32_t j = 0; j < REFS_PER_ROOT; ++j) + { + asset->GetAsset().refs.emplace_back(m_leafUuids[Random::get(0, m_leafUuids.size() - 1)]); + } + + batchBytes += asset->Pack().size(); + uuids.push_back(asset->GetHeader().uuid); + } + state.ResumeTiming(); + + benchmark::DoNotOptimize(SerializationManager::SaveAssets(uuids)); + bytesProcessed += static_cast(batchBytes); + } + + state.SetItemsProcessed(state.iterations() * batchSize); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SerializationManagerFixture, SaveAssets) + ->Name("SerializationManager/SaveAssets") + ->Arg(1) + ->Arg(8) + ->Arg(64) + ->Arg(512) + ->UseRealTime(); +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Tools/CMakeLists.txt b/Bigfoot/Benchmarks/Tools/CMakeLists.txt new file mode 100644 index 0000000..4f25147 --- /dev/null +++ b/Bigfoot/Benchmarks/Tools/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/SasquatchBaker) diff --git a/Bigfoot/Benchmarks/Tools/SasquatchBaker/CMakeLists.txt b/Bigfoot/Benchmarks/Tools/SasquatchBaker/CMakeLists.txt new file mode 100644 index 0000000..baa81a7 --- /dev/null +++ b/Bigfoot/Benchmarks/Tools/SasquatchBaker/CMakeLists.txt @@ -0,0 +1,11 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}Benchmarks) + +bigfoot_create_package_benchmarks( + "Tools") + +target_link_libraries(${PROJECT_NAME} + PRIVATE + $) + +bigfoot_create_bigfile("Benchmarks/Tools") diff --git a/Bigfoot/Benchmarks/Tools/SasquatchBaker/Include/SasquatchBakerBenchmarks/BigFileInfo_generated.hpp b/Bigfoot/Benchmarks/Tools/SasquatchBaker/Include/SasquatchBakerBenchmarks/BigFileInfo_generated.hpp new file mode 100644 index 0000000..a98dccd --- /dev/null +++ b/Bigfoot/Benchmarks/Tools/SasquatchBaker/Include/SasquatchBakerBenchmarks/BigFileInfo_generated.hpp @@ -0,0 +1,22 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file BigFileInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_SASQUATCHBAKERBENCHMARKS_BIGFILEINFO_GENERATED_HPP +#define BIGFOOT_SASQUATCHBAKERBENCHMARKS_BIGFILEINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_TOOLMODE + +namespace Bigfoot +{ +/* + * BigFile location + */ +constexpr eastl::string_view BIGFILE_SASQUATCHBAKERBENCHMARKS_LOCATION {"./SasquatchBakerBenchmarks.db"}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Benchmarks/Tools/SasquatchBaker/SasquatchBaker.cpp b/Bigfoot/Benchmarks/Tools/SasquatchBaker/SasquatchBaker.cpp new file mode 100644 index 0000000..f28a69d --- /dev/null +++ b/Bigfoot/Benchmarks/Tools/SasquatchBaker/SasquatchBaker.cpp @@ -0,0 +1,87 @@ +/********************************************************************* + * \file SasquatchBaker.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Bigfoot +{ +class SasquatchBakerFixture: public benchmark::Fixture +{ + protected: + static constexpr std::uint32_t ASSET_COUNT = 20'000; + static constexpr std::size_t ASSET_PAYLOAD_SIZE = 256; + + void SetUp(benchmark::State&) override + { + m_bigFile.Clear(); + m_outputFile.Delete(); + + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + uuids.reserve(ASSET_COUNT); + flatbuffers.reserve(ASSET_COUNT); + hardReferences.reserve(ASSET_COUNT); + + for (std::uint32_t i = 0; i < ASSET_COUNT; ++i) + { + BenchmarkAsset asset; + asset.SetName(asset.GetHeader().uuid.ToString()); + asset.GetAsset().payload.assign(ASSET_PAYLOAD_SIZE, std::uint8_t {0xAB}); + + const eastl::vector flatbuffer = asset.Pack(); + flatbuffers.push_back(flatbuffer); + hardReferences.push_back(asset.GetHardReferences(flatbuffer)); + uuids.push_back(asset.GetHeader().uuid); + } + + m_bigFile.SaveAssetFlatbuffers(uuids, flatbuffers, hardReferences); + } + + void TearDown(benchmark::State&) override + { + m_outputFile.Delete(); + } + + static constexpr eastl::string_view SASQUATCH_SASQUATCHBAKERBENCHMARKS_LOCATION { + "./SasquatchBakerBenchmarks.sasquatch"}; + BigFile m_bigFile {File {BIGFILE_SASQUATCHBAKERBENCHMARKS_LOCATION}}; + File m_outputFile {SASQUATCH_SASQUATCHBAKERBENCHMARKS_LOCATION}; +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(SasquatchBakerFixture, Bake)(benchmark::State& state) +{ + std::int64_t bytesProcessed = 0; + + for (auto _: state) + { + Bake(m_bigFile, m_outputFile); + bytesProcessed += static_cast(m_outputFile.GetSize()); + } + + state.SetItemsProcessed(state.iterations() * ASSET_COUNT); + state.SetBytesProcessed(bytesProcessed); +} + +BENCHMARK_REGISTER_F(SasquatchBakerFixture, Bake)->Name("SasquatchBaker/Bake")->Unit(benchmark::kMillisecond); +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Utils/Algorithm/Graph.cpp b/Bigfoot/Benchmarks/Utils/Algorithm/Graph.cpp new file mode 100644 index 0000000..bcc4492 --- /dev/null +++ b/Bigfoot/Benchmarks/Utils/Algorithm/Graph.cpp @@ -0,0 +1,92 @@ +/********************************************************************* + * \file Graph.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include +#include + +#include + +namespace Bigfoot +{ +using Random = effolkronium::random_static; + +class LayeredTopologicalSortFixture: public benchmark::Fixture +{ + protected: + /* + * Nodes are grouped into layers, and every node in a layer depends on up to DEPENDENCY_MAX_COUNT + * distinct nodes randomly picked from the previous layer. + */ + void SetUp(benchmark::State& state) override + { + Random::seed(0x5EEDU); + + const auto nodeCount = static_cast(state.range(0)); + + m_graph.clear(); + m_nodes.clear(); + m_graph.reserve(nodeCount); + m_nodes.reserve(nodeCount); + + for (std::uint32_t node = 0; node < nodeCount; ++node) + { + m_nodes.insert(node); + eastl::vector& edges = m_graph[node]; + + const std::uint32_t layerStart = (node / NODE_COUNT_PER_LAYER) * NODE_COUNT_PER_LAYER; + if (layerStart == 0) + { + continue; + } + + const std::uint32_t previousLayerStart = layerStart - NODE_COUNT_PER_LAYER; + ankerl::unordered_dense::set picked; + for (std::uint32_t i = 0; i < DEPENDENCY_MAX_COUNT; ++i) + { + const std::uint32_t dependency = Random::get(previousLayerStart, layerStart - 1); + if (picked.insert(dependency).second) + { + edges.push_back(dependency); + } + } + } + } + + static constexpr std::uint32_t NODE_COUNT_PER_LAYER = 64; + static constexpr std::uint32_t DEPENDENCY_MAX_COUNT = 4; + + ankerl::unordered_dense::map> m_graph; + ankerl::unordered_dense::set m_nodes; +}; + +/****************************************************************************************/ + +BENCHMARK_DEFINE_F(LayeredTopologicalSortFixture, LayeredTopologicalSortBench)(benchmark::State& state) +{ + const auto nodeCount = static_cast(state.range(0)); + + for (auto _: state) + { + eastl::vector> waves; + eastl::vector cyclicNodes; + benchmark::DoNotOptimize(LayeredTopologicalSort(m_graph, m_nodes, waves, cyclicNodes)); + benchmark::DoNotOptimize(waves); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * nodeCount); +} + +BENCHMARK_REGISTER_F(LayeredTopologicalSortFixture, LayeredTopologicalSortBench) + ->Name("Bigfoot::LayeredTopologicalSort") + ->Arg(512) + ->Arg(8192) + ->Arg(262'144) + ->Arg(4'194'304); +} // namespace Bigfoot diff --git a/Bigfoot/Benchmarks/Utils/CMakeLists.txt b/Bigfoot/Benchmarks/Utils/CMakeLists.txt new file mode 100644 index 0000000..53a5d66 --- /dev/null +++ b/Bigfoot/Benchmarks/Utils/CMakeLists.txt @@ -0,0 +1,5 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}Benchmarks) + +bigfoot_create_package_benchmarks( + "") \ No newline at end of file diff --git a/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp b/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp new file mode 100644 index 0000000..d0bd751 --- /dev/null +++ b/Bigfoot/Benchmarks/Utils/Containers/SlotMap.cpp @@ -0,0 +1,259 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Bigfoot +{ +using Random = effolkronium::random_static; + +struct MyComplexStruct +{ + std::uint32_t m_actualValueForBench = 0; // field summed by the Iterate benchmark + + float m_position[3] = {0.0f, 0.0f, 0.0f}; + float m_rotation[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + float m_scale[3] = {1.0f, 1.0f, 1.0f}; + + std::uint64_t m_entityId = 0; + std::uint32_t m_parentSlot = 0; + std::uint32_t m_flags = 0; + + MyComplexStruct() = default; + + explicit MyComplexStruct(const std::uint32_t p_value): + m_actualValueForBench(p_value), + m_entityId(p_value) + { + } +}; + +static_assert(sizeof(MyComplexStruct) == 64, "Payload should be exactly one cache line."); +static_assert(std::is_trivially_copyable_v, + "Container moves should be a plain memcpy, not a heap-touching move."); + +template +class SlotMapAdaptor +{ + public: + using Key = SLOTMAP::SlotMapKey; + + Key Add(MyComplexStruct&& p_value) + { + return m_container.Insert(std::move(p_value)); + } + + const MyComplexStruct* Find(const Key p_key) const + { + return m_container.Get(p_key); + } + + void Remove(const Key p_key) + { + m_container.Remove(p_key); + } + + void Clear() + { + m_container.Reset(); + } + + template + void ForEachValue(FUNC&& p_func) const + { + for (const MyComplexStruct& value: m_container) + { + p_func(value); + } + } + + private: + SLOTMAP m_container; +}; + +template +class HashMapHarnessAdaptor +{ + public: + using Key = typename MAP::key_type; + + Key Add(MyComplexStruct&& p_value) + { + const Key key = m_nextKey++; + m_container.emplace(key, std::move(p_value)); + return key; + } + + const MyComplexStruct* Find(const Key p_key) const + { + const auto it = m_container.find(p_key); + return it != m_container.end() ? &it->second : nullptr; + } + + void Remove(const Key p_key) + { + m_container.erase(p_key); + } + + void Clear() + { + m_container.clear(); + m_nextKey = 0; + } + + template + void ForEachValue(FUNC&& p_func) const + { + for (const auto& keyValue: m_container) + { + p_func(keyValue.second); + } + } + + private: + MAP m_container; + Key m_nextKey = 0; +}; + +template +void Insert(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + for (auto _: state) + { + ADAPTOR adaptor; + for (std::uint32_t i = 0; i < count; ++i) + { + benchmark::DoNotOptimize(adaptor.Add(MyComplexStruct {i})); + } + benchmark::DoNotOptimize(adaptor); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(MyComplexStruct))); +} + +template +void Remove(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + ADAPTOR adaptor; + std::vector keys; + keys.reserve(count); + Random::seed(0x5EEDU); + + for (auto _: state) + { + state.PauseTiming(); + adaptor.Clear(); + keys.clear(); + for (std::uint32_t i = 0; i < count; ++i) + { + keys.push_back(adaptor.Add(MyComplexStruct {i})); + } + Random::shuffle(keys); + state.ResumeTiming(); + + for (const typename ADAPTOR::Key key: keys) + { + adaptor.Remove(key); + } + benchmark::DoNotOptimize(adaptor); + benchmark::ClobberMemory(); + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(MyComplexStruct))); +} + +template +void Get(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + ADAPTOR adaptor; + std::vector keys; + keys.reserve(count); + for (std::uint32_t i = 0; i < count; ++i) + { + keys.push_back(adaptor.Add(MyComplexStruct {i})); + } + Random::seed(0x5EEDU); + Random::shuffle(keys); + + for (auto _: state) + { + std::uint64_t sum = 0; + for (const typename ADAPTOR::Key key: keys) + { + if (const MyComplexStruct* const value = adaptor.Find(key)) + { + sum += value->m_actualValueForBench; + } + } + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(MyComplexStruct))); +} + +template +void Iterate(benchmark::State& state) +{ + const auto count = static_cast(state.range(0)); + + ADAPTOR adaptor; + for (std::uint32_t i = 0; i < count; ++i) + { + adaptor.Add(MyComplexStruct {i}); + } + + for (auto _: state) + { + std::uint64_t sum = 0; + adaptor.ForEachValue( + [&sum](const MyComplexStruct& value) + { + sum += value.m_actualValueForBench; + }); + benchmark::DoNotOptimize(sum); + } + + state.SetItemsProcessed(state.iterations() * count); + state.SetBytesProcessed(static_cast(state.iterations() * count * sizeof(MyComplexStruct))); +} + +#define BIGFOOT_REGISTER_BENCHMARKS(ADAPTOR, NAME) \ + BENCHMARK_TEMPLATE(Insert, ADAPTOR)->Name(NAME "/Insert")->Arg(512)->Arg(8192)->Arg(262'144)->Arg(4'194'304); \ + BENCHMARK_TEMPLATE(Remove, ADAPTOR)->Name(NAME "/Remove")->Arg(512)->Arg(8192)->Arg(262'144)->Arg(4'194'304); \ + BENCHMARK_TEMPLATE(Get, ADAPTOR)->Name(NAME "/Get")->Arg(512)->Arg(8192)->Arg(262'144)->Arg(4'194'304); \ + BENCHMARK_TEMPLATE(Iterate, ADAPTOR)->Name(NAME "/Iterate")->Arg(512)->Arg(8192)->Arg(262'144)->Arg(4'194'304); + +using DenseSlotMapHarness = SlotMapAdaptor>; +using StableSlotMapHarness = SlotMapAdaptor>; +using UnorderedDenseMapHarness = HashMapHarnessAdaptor>; +using UnorderedDenseSegementedMapHarness = + HashMapHarnessAdaptor>; +using UnorderedMapHarness = HashMapHarnessAdaptor>; +using MapHarness = HashMapHarnessAdaptor>; + +BIGFOOT_REGISTER_BENCHMARKS(DenseSlotMapHarness, "Bigfoot::DenseSlotMap"); +BIGFOOT_REGISTER_BENCHMARKS(StableSlotMapHarness, "Bigfoot::StableSlotMap"); +BIGFOOT_REGISTER_BENCHMARKS(UnorderedDenseMapHarness, "ankerl::unordered_dense::map"); +BIGFOOT_REGISTER_BENCHMARKS(UnorderedDenseSegementedMapHarness, "ankerl::unordered_dense::segmented_map"); +BIGFOOT_REGISTER_BENCHMARKS(UnorderedMapHarness, "std::unordered_map"); +BIGFOOT_REGISTER_BENCHMARKS(MapHarness, "std::map"); + +#undef BIGFOOT_REGISTER_BENCHMARKS +#undef BIGFOOT_CACHE_TIER_ARGS +} // namespace Bigfoot 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/Asset/AssetBase.cpp b/Bigfoot/Sources/Engine/Asset/AssetBase.cpp new file mode 100644 index 0000000..17a34b4 --- /dev/null +++ b/Bigfoot/Sources/Engine/Asset/AssetBase.cpp @@ -0,0 +1,62 @@ +/********************************************************************* + * \file AssetBase.cpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#include + +#include + +namespace Bigfoot +{ +void AssetBase::HardReferenceVisitor::Field([[maybe_unused]] size_t p_fieldIndex, + [[maybe_unused]] size_t p_setIndex, + const flatbuffers::ElementaryType p_type, + const bool p_isVector, + const flatbuffers::TypeTable* p_typeTable, + [[maybe_unused]] const char* p_name, + const std::uint8_t* p_val) +{ + if (!p_isVector) + { + Visit(p_type, p_typeTable, p_val); + } +} + +/****************************************************************************************/ + +void AssetBase::HardReferenceVisitor::Element([[maybe_unused]] size_t p_index, + const flatbuffers::ElementaryType p_type, + const flatbuffers::TypeTable* p_typeTable, + const std::uint8_t* p_val) +{ + Visit(p_type, p_typeTable, p_val); +} + +/****************************************************************************************/ + +eastl::vector AssetBase::HardReferenceVisitor::GetUUIDs() const +{ + eastl::vector uuids; + uuids.reserve(m_uuids.size()); + std::copy(m_uuids.begin(), m_uuids.end(), eastl::back_inserter(uuids)); + return uuids; +} + +/****************************************************************************************/ + +void AssetBase::HardReferenceVisitor::Visit(const flatbuffers::ElementaryType p_type, + const flatbuffers::TypeTable* p_typeTable, + const std::uint8_t* p_val) +{ + if (p_val == nullptr || p_type != flatbuffers::ET_SEQUENCE || + p_typeTable != ::Flat::Bigfoot::HardReference::MiniReflectTypeTable()) + { + return; + } + + const ::Flat::Bigfoot::HardReference* hardReference = std::bit_cast(p_val); + m_uuids.emplace(flatbuffers::UnPack(hardReference->uuid())); +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp b/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp new file mode 100644 index 0000000..5a94c2e --- /dev/null +++ b/Bigfoot/Sources/Engine/Asset/AssetContainer.cpp @@ -0,0 +1,188 @@ +/********************************************************************* + * \file AssetContainer.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +namespace Bigfoot +{ +ContainerHandle::ContainerHandle(AssetContainerBase* p_assetContainer, const UUID p_uuid): + m_assetContainer(p_assetContainer), + m_uuid(p_uuid) +{ +} + +/****************************************************************************************/ + +std::uint32_t ContainerHandle::GetHardRefCount() const +{ + return m_hardRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::IncrementHardRefCount() +{ + if (m_hardRefCount == 0) + { + m_assetContainer->CancelRemoval(m_uuid); + } + ++m_hardRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::DecrementHardRefCount() +{ + --m_hardRefCount; + if (m_hardRefCount == 0) + { + m_assetContainer->QueueForRemoval(m_uuid); + } +} + +/****************************************************************************************/ + +std::uint32_t ContainerHandle::GetSoftRefCount() const +{ + return m_softRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::IncrementSoftRefCount() +{ + ++m_softRefCount; +} + +/****************************************************************************************/ + +void ContainerHandle::DecrementSoftRefCount() +{ + --m_softRefCount; +} + +/****************************************************************************************/ + +AssetBase* ContainerHandle::GetAsset() +{ + return m_asset; +} + +/****************************************************************************************/ + +const AssetBase* ContainerHandle::GetAsset() const +{ + return m_asset; +} + +/****************************************************************************************/ + +void ContainerHandle::SetAsset(AssetBase* p_asset) +{ + m_asset = p_asset; +} + +/****************************************************************************************/ + +bool AssetContainerBase::Has(const UUID& p_uuid) const +{ + const Entry* entry = FindEntry(p_uuid); + return entry != nullptr && entry->m_assetSlotKey.Valid(); +} + +/****************************************************************************************/ + +ContainerHandle* AssetContainerBase::AcquireContainerHandle(const UUID& p_uuid) +{ + return GetOrCreateEntry(p_uuid).m_handle; +} + +/****************************************************************************************/ + +void AssetContainerBase::Remove(const UUID& p_uuid) +{ + if (Entry* entry = FindEntry(p_uuid); entry != nullptr) + { + if (entry->m_assetSlotKey.Valid()) + { + entry->m_handle->SetAsset(nullptr); + RemoveAsset(entry->m_assetSlotKey); + entry->m_assetSlotKey = SlotMapKey {}; + } + m_handles.Remove(entry->m_handleSlotKey); + m_uuidToEntry.erase(p_uuid); + } +} + +/****************************************************************************************/ + +void AssetContainerBase::QueueForRemoval(const UUID& p_uuid) +{ + m_pendingRemovalUuids.push_back(p_uuid); +} + +/****************************************************************************************/ + +void AssetContainerBase::CancelRemoval(const UUID& p_uuid) +{ + auto* const pendingIt = eastl::find(m_pendingRemovalUuids.begin(), m_pendingRemovalUuids.end(), p_uuid); + if (pendingIt != m_pendingRemovalUuids.end()) + { + m_pendingRemovalUuids.erase_unsorted(pendingIt); + } +} + +/****************************************************************************************/ + +void AssetContainerBase::ProcessPendingRemovals() +{ + eastl::vector toRemove; + toRemove.swap(m_pendingRemovalUuids); + + for (const UUID& uuid: toRemove) + { + Remove(uuid); + } +} + +/****************************************************************************************/ + +bool AssetContainerBase::IsMarkedForRemove(const UUID& p_uuid) +{ + return eastl::find(m_pendingRemovalUuids.begin(), m_pendingRemovalUuids.end(), p_uuid) != + m_pendingRemovalUuids.end(); +} + +/****************************************************************************************/ + +AssetContainerBase::Entry* AssetContainerBase::FindEntry(const UUID& p_uuid) +{ + const auto entryIt = m_uuidToEntry.find(p_uuid); + return entryIt != m_uuidToEntry.end() ? &entryIt->second : nullptr; +} + +/****************************************************************************************/ + +const AssetContainerBase::Entry* AssetContainerBase::FindEntry(const UUID& p_uuid) const +{ + const auto entryIt = m_uuidToEntry.find(p_uuid); + return entryIt != m_uuidToEntry.end() ? &entryIt->second : nullptr; +} + +/****************************************************************************************/ + +AssetContainerBase::Entry& AssetContainerBase::GetOrCreateEntry(const UUID& p_uuid) +{ + auto [entryIt, inserted] = m_uuidToEntry.try_emplace(p_uuid); + if (inserted) + { + entryIt->second.m_handleSlotKey = m_handles.Insert(this, p_uuid); + entryIt->second.m_handle = m_handles.Get(entryIt->second.m_handleSlotKey); + } + + return entryIt->second; +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp b/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp new file mode 100644 index 0000000..5f8d23c --- /dev/null +++ b/Bigfoot/Sources/Engine/Asset/AssetLibrary.cpp @@ -0,0 +1,86 @@ +/********************************************************************* + * \file AssetLibrary.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +namespace Bigfoot +{ +AssetContainerBase* AssetLibrary::GetAssetContainer(const AssetTypeID p_typeID) +{ + if (const auto container = m_assetContainers.find(p_typeID); container != m_assetContainers.end()) + { + return container->second; + } + + return nullptr; +} + +/****************************************************************************************/ + +const AssetContainerBase* AssetLibrary::GetAssetContainer(const AssetTypeID p_typeID) const +{ + if (const auto container = m_assetContainers.find(p_typeID); container != m_assetContainers.end()) + { + return container->second; + } + + return nullptr; +} + +/****************************************************************************************/ + +const AssetContainerBase* AssetLibrary::GetAssetContainer(const UUID& p_uuid) const +{ + for (const auto [typeId, container]: m_assetContainers) + { + if (container->Has(p_uuid)) + { + return container; + } + } + + return nullptr; +} + +/****************************************************************************************/ + +AssetContainerBase* AssetLibrary::GetAssetContainer(const UUID& p_uuid) +{ + for (const auto [typeId, container]: m_assetContainers) + { + if (container->Has(p_uuid)) + { + return container; + } + } + + return nullptr; +} + +/****************************************************************************************/ + +bool AssetLibrary::IsLoaded(const UUID& p_uuid) const +{ + return GetAssetContainer(p_uuid) != nullptr; +} + +/****************************************************************************************/ + +void AssetLibrary::ProcessPendingRemovals() +{ + for (const auto [typeId, container]: m_assetContainers) + { + container->ProcessPendingRemovals(); + } +} + +/****************************************************************************************/ + +void SetActiveAssetLibrary(AssetLibrary* p_assetLibrary) +{ + g_activeAssetLibrary = p_assetLibrary; +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Asset/AssetTypeID.cpp b/Bigfoot/Sources/Engine/Asset/AssetTypeID.cpp new file mode 100644 index 0000000..9cf79bd --- /dev/null +++ b/Bigfoot/Sources/Engine/Asset/AssetTypeID.cpp @@ -0,0 +1,24 @@ +/********************************************************************* + * \file AssetTypeID.cpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#include + +#include + +namespace flatbuffers +{ +Flat::Bigfoot::AssetTypeID Pack(const Bigfoot::AssetTypeID& p_assetTypeID) +{ + return Flat::Bigfoot::AssetTypeID {p_assetTypeID}; +} + +/****************************************************************************************/ + +Bigfoot::AssetTypeID UnPack(const Flat::Bigfoot::AssetTypeID& p_flatAssetTypeID) +{ + return Bigfoot::AssetTypeID {p_flatAssetTypeID.value()}; +} +} // namespace flatbuffers diff --git a/Bigfoot/Sources/Engine/BigFile/BigFile.cpp b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp new file mode 100644 index 0000000..c3efdee --- /dev/null +++ b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp @@ -0,0 +1,851 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_TOOLMODE +BigFile::BigFile(const File& p_file) +{ + [[maybe_unused]] + const int result = sqlite3_open_v2(p_file.ToAbsolute().GetPath().data(), &m_db, SQLITE_OPEN_READWRITE, nullptr); + CRITICAL_ASSERT(EngineAssertHandler, + result == SQLITE_OK, + "Failed to open BigFile {} DB: {}", + p_file.ToAbsolute().GetPath(), + sqlite3_errmsg(m_db)); + + [[maybe_unused]] + const int synchronousResult = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, + synchronousResult == SQLITE_OK, + "Failed to set synchronous pragma: {}", + sqlite3_errmsg(m_db)); + + [[maybe_unused]] + const int foreignKeysResult = sqlite3_exec(m_db, "PRAGMA foreign_keys = ON;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, + foreignKeysResult == SQLITE_OK, + "Failed to set foreign_keys pragma: {}", + sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::BeginTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to begin transaction: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::CommitTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "COMMIT TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to commit: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::RollbackTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "ROLLBACK TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to rollback: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Clear() +{ + BeginTransaction(); + + BigFile::Request {*this, "DELETE FROM AssetHardReference;"}.Execute(); + BigFile::Request {*this, "DELETE FROM Asset;"}.Execute(); + BigFile::Request {*this, "DELETE FROM AssetMetadata;"}.Execute(); + + CommitTransaction(); +} + +/****************************************************************************************/ + +BigFile::~BigFile() +{ + [[maybe_unused]] + const int result = sqlite3_close_v2(m_db); + CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to close BigFile DB: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +eastl::vector BigFile::GetAssetFlatbuffer(const UUID& p_uuid) const +{ + BigFile::Request assetRequest {*this, "SELECT Asset FROM Asset WHERE UUID = ?1;"}; + assetRequest.Bind(1, p_uuid.ToBytes()); + + if (!assetRequest.Step()) + { + return {}; + } + + const eastl::span flatbuffer = assetRequest.Get(0); + return eastl::vector {flatbuffer.begin(), flatbuffer.end()}; +} + +/****************************************************************************************/ + +eastl::vector> BigFile::GetAssetFlatbuffers(const eastl::span p_uuids) const +{ + eastl::vector> flatbuffers(p_uuids.size()); + + if (p_uuids.empty()) + { + return flatbuffers; + } + + ankerl::unordered_dense::map> uuidToIndices; + uuidToIndices.reserve(p_uuids.size()); + for (std::size_t i = 0; i < p_uuids.size(); ++i) + { + uuidToIndices[p_uuids[i]].push_back(i); + } + + eastl::vector distinctUUIDs; + distinctUUIDs.reserve(uuidToIndices.size()); + for (const auto& [uuid, indices]: uuidToIndices) + { + distinctUUIDs.push_back(uuid); + } + + constexpr std::size_t PARAMS_PER_ROW = 1; + const std::size_t maxRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + PARAMS_PER_ROW; + + for (std::size_t chunkStart = 0; chunkStart < distinctUUIDs.size(); chunkStart += maxRowsPerStatement) + { + const std::size_t chunkSize = eastl::min(maxRowsPerStatement, distinctUUIDs.size() - chunkStart); + + eastl::string placeholders; + placeholders.reserve(chunkSize * 2); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + placeholders += ", "; + } + placeholders += "?"; + } + + const eastl::string sql = Format("SELECT UUID, Asset FROM Asset WHERE UUID IN ({});", placeholders); + + BigFile::Request assetRequest {*this, sql}; + for (std::size_t i = 0; i < chunkSize; ++i) + { + assetRequest.Bind(static_cast(i + 1), distinctUUIDs[chunkStart + i].ToBytes()); + } + + while (assetRequest.Step()) + { + const std::span rawUUID { + static_cast>(assetRequest.Get(0))}; + const UUID uuid {rawUUID}; + + const auto indicesIt = uuidToIndices.find(uuid); + ASSERT(EngineAssertHandler, + indicesIt != uuidToIndices.end(), + "Returned UUID {} not found in requested UUIDs", + uuid); + + const eastl::span flatbuffer = assetRequest.Get(1); + + for (const std::size_t index: indicesIt->second) + { + flatbuffers[index].assign(flatbuffer.begin(), flatbuffer.end()); + } + } + } + + return flatbuffers; +} + +/****************************************************************************************/ + +eastl::vector BigFile::GetAssetHardReferences(const UUID& p_uuid) const +{ + BigFile::Request hardReferencesRequest {*this, "SELECT HardReference FROM AssetHardReference WHERE UUID = ?1;"}; + hardReferencesRequest.Bind(1, p_uuid.ToBytes()); + + eastl::vector hardReferences; + while (hardReferencesRequest.Step()) + { + const std::span rawUUID { + static_cast>(hardReferencesRequest.Get(0))}; + hardReferences.emplace_back(rawUUID); + } + + return hardReferences; +} + +/****************************************************************************************/ + +eastl::vector> BigFile::GetAssetsHardReferences(eastl::span p_uuids) const +{ + eastl::vector> hardReferences(p_uuids.size()); + + if (p_uuids.empty()) + { + return hardReferences; + } + + ankerl::unordered_dense::map> uuidToIndices; + uuidToIndices.reserve(p_uuids.size()); + for (std::size_t i = 0; i < p_uuids.size(); ++i) + { + uuidToIndices[p_uuids[i]].push_back(i); + } + + eastl::vector distinctUUIDs; + distinctUUIDs.reserve(uuidToIndices.size()); + for (const auto& [uuid, indices]: uuidToIndices) + { + distinctUUIDs.push_back(uuid); + } + + constexpr std::size_t PARAMS_PER_ROW = 1; + const std::size_t maxRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + PARAMS_PER_ROW; + + for (std::size_t chunkStart = 0; chunkStart < distinctUUIDs.size(); chunkStart += maxRowsPerStatement) + { + const std::size_t chunkSize = eastl::min(maxRowsPerStatement, distinctUUIDs.size() - chunkStart); + + eastl::string placeholders; + placeholders.reserve(chunkSize * 2); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + placeholders += ", "; + } + placeholders += "?"; + } + + const eastl::string sql = Format("SELECT UUID, HardReference FROM AssetHardReference WHERE UUID IN ({});", + placeholders); + + BigFile::Request hardReferencesRequest {*this, sql}; + for (std::size_t i = 0; i < chunkSize; ++i) + { + hardReferencesRequest.Bind(static_cast(i + 1), distinctUUIDs[chunkStart + i].ToBytes()); + } + + while (hardReferencesRequest.Step()) + { + const std::span rawOwnerUUID { + static_cast>(hardReferencesRequest.Get(0))}; + const UUID ownerUUID {rawOwnerUUID}; + + const auto indicesIt = uuidToIndices.find(ownerUUID); + ASSERT(EngineAssertHandler, + indicesIt != uuidToIndices.end(), + "Returned UUID {} not found in requested UUIDs", + ownerUUID); + + const std::span rawHardReference { + static_cast>(hardReferencesRequest.Get(1))}; + + for (const std::size_t index: indicesIt->second) + { + hardReferences[index].emplace_back(rawHardReference); + } + } + } + + return hardReferences; +} + +/****************************************************************************************/ + +eastl::optional BigFile::GetAssetTime(const UUID& p_uuid) const +{ + BigFile::Request timeRequest {*this, "SELECT CreateTime, ModificationTime FROM AssetMetadata WHERE UUID = ?1;"}; + timeRequest.Bind(1, p_uuid.ToBytes()); + + if (!timeRequest.Step()) + { + return eastl::nullopt; + } + + return AssetTime {.m_creationTime = Time {static_cast(timeRequest.Get(0))}, + .m_modificationTime = Time {static_cast(timeRequest.Get(1))}}; +} + +/****************************************************************************************/ + +eastl::vector> BigFile::GetAssetsTime(const eastl::span p_uuids) const +{ + eastl::vector> times(p_uuids.size()); + + if (p_uuids.empty()) + { + return times; + } + + ankerl::unordered_dense::map> uuidToIndices; + uuidToIndices.reserve(p_uuids.size()); + for (std::size_t i = 0; i < p_uuids.size(); ++i) + { + uuidToIndices[p_uuids[i]].push_back(i); + } + + eastl::vector distinctUUIDs; + distinctUUIDs.reserve(uuidToIndices.size()); + for (const auto& [uuid, indices]: uuidToIndices) + { + distinctUUIDs.push_back(uuid); + } + + constexpr std::size_t PARAMS_PER_ROW = 1; + const std::size_t maxRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + PARAMS_PER_ROW; + + for (std::size_t chunkStart = 0; chunkStart < distinctUUIDs.size(); chunkStart += maxRowsPerStatement) + { + const std::size_t chunkSize = eastl::min(maxRowsPerStatement, distinctUUIDs.size() - chunkStart); + + eastl::string placeholders; + placeholders.reserve(chunkSize * 2); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + placeholders += ", "; + } + placeholders += "?"; + } + + const eastl::string sql = Format( + "SELECT UUID, CreateTime, ModificationTime FROM AssetMetadata WHERE UUID IN ({});", + placeholders); + + BigFile::Request timeRequest {*this, sql}; + for (std::size_t i = 0; i < chunkSize; ++i) + { + timeRequest.Bind(static_cast(i + 1), distinctUUIDs[chunkStart + i].ToBytes()); + } + + while (timeRequest.Step()) + { + const std::span rawUUID { + static_cast>(timeRequest.Get(0))}; + const UUID uuid {rawUUID}; + + const auto indicesIt = uuidToIndices.find(uuid); + ASSERT(EngineAssertHandler, + indicesIt != uuidToIndices.end(), + "Returned UUID {} not found in requested UUIDs", + uuid); + + const AssetTime assetTime {.m_creationTime = Time {static_cast(timeRequest.Get(1))}, + .m_modificationTime = Time {static_cast(timeRequest.Get(2))}}; + + for (const std::size_t index: indicesIt->second) + { + times[index] = assetTime; + } + } + } + + return times; +} + +/****************************************************************************************/ + +void BigFile::SaveAssetFlatbuffer(const UUID& p_uuid, + const eastl::span p_flatbuffer, + const eastl::span p_hardReferences) +{ + BeginTransaction(); + + BigFile::Request metadataRequest {*this, + "INSERT INTO AssetMetadata (UUID) VALUES (?1) " + "ON CONFLICT(UUID) DO NOTHING;"}; + metadataRequest.Bind(1, p_uuid.ToBytes()); + 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_uuid.ToBytes()); + assetRequest.Bind(2, p_flatbuffer); + assetRequest.Execute(); + + BigFile::Request deleteHardReferencesRequest {*this, "DELETE FROM AssetHardReference WHERE UUID = ?1;"}; + deleteHardReferencesRequest.Bind(1, p_uuid.ToBytes()); + deleteHardReferencesRequest.Execute(); + + constexpr std::size_t HARDREF_PARAMS_PER_ROW = 2; + constexpr std::size_t HARDREF_PLACEHOLDER_RESERVE_CHARS_PER_ROW = 8; + const std::size_t maxHardRefRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + HARDREF_PARAMS_PER_ROW; + + for (std::size_t chunkStart = 0; chunkStart < p_hardReferences.size(); chunkStart += maxHardRefRowsPerStatement) + { + const std::size_t chunkSize = eastl::min(maxHardRefRowsPerStatement, p_hardReferences.size() - chunkStart); + + eastl::string hardReferencePlaceholders; + hardReferencePlaceholders.reserve(chunkSize * HARDREF_PLACEHOLDER_RESERVE_CHARS_PER_ROW); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + hardReferencePlaceholders += ", "; + } + hardReferencePlaceholders += "(?, ?)"; + } + + const eastl::string hardReferenceSQL = Format("INSERT INTO AssetHardReference (UUID, HardReference) VALUES {} " + "ON CONFLICT(UUID, HardReference) DO NOTHING;", + hardReferencePlaceholders); + + BigFile::Request hardReferenceRequest {*this, hardReferenceSQL}; + std::uint32_t bindIndex = 1; + for (std::size_t i = 0; i < chunkSize; ++i) + { + hardReferenceRequest.Bind(bindIndex++, p_uuid.ToBytes()); + hardReferenceRequest.Bind(bindIndex++, p_hardReferences[chunkStart + i].ToBytes()); + } + hardReferenceRequest.Execute(); + } + + CommitTransaction(); +} + +/****************************************************************************************/ + +void BigFile::SaveAssetFlatbuffers(const eastl::span p_uuids, + const eastl::span> p_flatbuffers, + const eastl::span> p_hardReferences) +{ + ASSERT(EngineAssertHandler, + p_uuids.size() == p_flatbuffers.size() && p_uuids.size() == p_hardReferences.size(), + "p_metadatas and p_flatbuffers and p_hardReferences must have the same size"); + + if (p_uuids.empty()) + { + return; + } + + BeginTransaction(); + + constexpr std::size_t METADATA_PARAMS_PER_ROW = 4; + constexpr std::size_t METADATA_PLACEHOLDER_RESERVE_CHARS_PER_ROW = 14; + constexpr std::size_t ASSET_PLACEHOLDER_RESERVE_CHARS_PER_ROW = 8; + const std::size_t maxRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + METADATA_PARAMS_PER_ROW; + + for (std::size_t chunkStart = 0; chunkStart < p_uuids.size(); chunkStart += maxRowsPerStatement) + { + const std::size_t chunkSize = eastl::min(maxRowsPerStatement, p_uuids.size() - chunkStart); + + eastl::string metadataPlaceholders; + metadataPlaceholders.reserve(chunkSize * METADATA_PLACEHOLDER_RESERVE_CHARS_PER_ROW); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + metadataPlaceholders += ", "; + } + metadataPlaceholders += "(?)"; + } + + const eastl::string metadataSQL = Format( + "INSERT INTO AssetMetadata (UUID) VALUES {} ON CONFLICT(UUID) DO NOTHING;", + metadataPlaceholders); + + BigFile::Request metadataRequest {*this, metadataSQL}; + std::uint32_t metadataBindIndex = 1; + for (std::size_t i = 0; i < chunkSize; ++i) + { + metadataRequest.Bind(metadataBindIndex++, p_uuids[chunkStart + i].ToBytes()); + } + metadataRequest.Execute(); + + eastl::string assetPlaceholders; + assetPlaceholders.reserve(chunkSize * ASSET_PLACEHOLDER_RESERVE_CHARS_PER_ROW); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + assetPlaceholders += ", "; + } + assetPlaceholders += "(?, ?)"; + } + + const eastl::string assetSQL = Format( + "INSERT INTO Asset (UUID, Asset) VALUES {} ON CONFLICT(UUID) DO UPDATE SET Asset = excluded.Asset;", + assetPlaceholders); + + BigFile::Request assetRequest {*this, assetSQL}; + std::uint32_t assetBindIndex = 1; + for (std::size_t i = 0; i < chunkSize; ++i) + { + assetRequest.Bind(assetBindIndex++, p_uuids[chunkStart + i].ToBytes()); + assetRequest.Bind(assetBindIndex++, p_flatbuffers[chunkStart + i]); + } + assetRequest.Execute(); + + // Replace hard references for every asset in this chunk with its current set. + eastl::string deleteInPlaceholders; + deleteInPlaceholders.reserve(chunkSize * 3); + for (std::size_t i = 0; i < chunkSize; ++i) + { + if (i > 0) + { + deleteInPlaceholders += ", "; + } + deleteInPlaceholders += "?"; + } + + const eastl::string deleteHardReferencesSQL = Format("DELETE FROM AssetHardReference WHERE UUID IN ({});", + deleteInPlaceholders); + + BigFile::Request deleteHardReferencesRequest {*this, deleteHardReferencesSQL}; + for (std::size_t i = 0; i < chunkSize; ++i) + { + deleteHardReferencesRequest.Bind(static_cast(i + 1), p_uuids[chunkStart + i].ToBytes()); + } + deleteHardReferencesRequest.Execute(); + + eastl::vector> hardReferencePairs; + for (std::size_t i = 0; i < chunkSize; ++i) + { + for (const UUID& hardReference: p_hardReferences[chunkStart + i]) + { + hardReferencePairs.emplace_back(p_uuids[chunkStart + i], hardReference); + } + } + + constexpr std::size_t HARDREF_PARAMS_PER_ROW = 2; + constexpr std::size_t HARDREF_PLACEHOLDER_RESERVE_CHARS_PER_ROW = 8; + const std::size_t maxHardRefRowsPerStatement = static_cast( + sqlite3_limit(m_db, SQLITE_LIMIT_VARIABLE_NUMBER, -1)) / + HARDREF_PARAMS_PER_ROW; + + for (std::size_t hardRefChunkStart = 0; hardRefChunkStart < hardReferencePairs.size(); + hardRefChunkStart += maxHardRefRowsPerStatement) + { + const std::size_t hardRefChunkSize = eastl::min(maxHardRefRowsPerStatement, + hardReferencePairs.size() - hardRefChunkStart); + + eastl::string hardReferencePlaceholders; + hardReferencePlaceholders.reserve(hardRefChunkSize * HARDREF_PLACEHOLDER_RESERVE_CHARS_PER_ROW); + for (std::size_t i = 0; i < hardRefChunkSize; ++i) + { + if (i > 0) + { + hardReferencePlaceholders += ", "; + } + hardReferencePlaceholders += "(?, ?)"; + } + + const eastl::string hardReferenceSQL = Format( + "INSERT INTO AssetHardReference (UUID, HardReference) VALUES {} " + "ON CONFLICT(UUID, HardReference) DO NOTHING;", + hardReferencePlaceholders); + + BigFile::Request hardReferenceRequest {*this, hardReferenceSQL}; + std::uint32_t bindIndex = 1; + for (std::size_t i = 0; i < hardRefChunkSize; ++i) + { + const auto& [assetUUID, hardReference] = hardReferencePairs[hardRefChunkStart + i]; + hardReferenceRequest.Bind(bindIndex++, assetUUID.ToBytes()); + hardReferenceRequest.Bind(bindIndex++, hardReference.ToBytes()); + } + hardReferenceRequest.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) +{ + [[maybe_unused]] + const int result = sqlite3_prepare_v2(m_db, + p_request.data(), + static_cast(p_request.size()), + &m_statement, + nullptr); + CRITICAL_ASSERT(EngineAssertHandler, + result == SQLITE_OK, + "Failed to create statement from BigFile DB: {}", + sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::int32_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int(m_statement, static_cast(p_index), p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::uint32_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int(m_statement, static_cast(p_index), static_cast(p_value)); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::int64_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int64(m_statement, static_cast(p_index), p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::uint64_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int64(m_statement, static_cast(p_index), std::bit_cast(p_value)); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const float p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_double(m_statement, static_cast(p_index), p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const double p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_double(m_statement, static_cast(p_index), p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const eastl::string_view p_value, const CopyValue p_copy) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_text(m_statement, + static_cast(p_index), + p_value.data(), + static_cast(p_value.size()), + p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, + const eastl::span p_value, + const CopyValue p_copy) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1U) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_blob(m_statement, + static_cast(p_index), + p_value.data(), + static_cast(p_value.size()), + p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +bool BigFile::Request::Step() +{ + const int result = sqlite3_step(m_statement); + ASSERT(EngineAssertHandler, + (result == SQLITE_DONE) || (result == SQLITE_ROW), + "Failed to step through the statement: {}", + sqlite3_errmsg(m_db)); + + return result == SQLITE_ROW; +} + +/****************************************************************************************/ + +std::uint32_t BigFile::Request::Execute() +{ + [[maybe_unused]] + const int result = sqlite3_step(m_statement); + ASSERT(EngineAssertHandler, (result == SQLITE_DONE), "Failed to execute the statement: {}", sqlite3_errmsg(m_db)); + + return sqlite3_changes(m_db); +} + +/****************************************************************************************/ + +BigFile::Request::Column::Column(const Request& p_request, const std::uint32_t p_index): + m_statement(p_request.m_statement), + m_index(p_index) +{ +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::int32_t() const +{ + return sqlite3_column_int(m_statement, static_cast(m_index)); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::uint32_t() const +{ + return sqlite3_column_int(m_statement, static_cast(m_index)); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::int64_t() const +{ + return sqlite3_column_int64(m_statement, static_cast(m_index)); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::uint64_t() const +{ + return std::bit_cast(sqlite3_column_int64(m_statement, static_cast(m_index))); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator float() const +{ + return static_cast(sqlite3_column_double(m_statement, static_cast(m_index))); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator double() const +{ + return sqlite3_column_double(m_statement, static_cast(m_index)); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator eastl::string_view() const +{ + return eastl::string_view { + reinterpret_cast(sqlite3_column_text(m_statement, static_cast(m_index))), + static_cast(sqlite3_column_bytes(m_statement, static_cast(m_index)))}; +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator eastl::span() const +{ + return eastl::span { + static_cast(sqlite3_column_blob(m_statement, static_cast(m_index))), + static_cast(sqlite3_column_bytes(m_statement, static_cast(m_index)))}; +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::span() const +{ + return std::span { + static_cast(sqlite3_column_blob(m_statement, static_cast(m_index))), + static_cast(sqlite3_column_bytes(m_statement, static_cast(m_index)))}; +} + +/****************************************************************************************/ + +BigFile::Request::Column BigFile::Request::Get(const std::uint32_t p_index) const +{ + ASSERT(EngineAssertHandler, + p_index < static_cast(sqlite3_column_count(m_statement)), + "Invalid index for column!"); + return {*this, p_index}; +} + +/****************************************************************************************/ + +BigFile::Request::~Request() +{ + [[maybe_unused]] + const int result = sqlite3_finalize(m_statement); + CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db)); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql b/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql index e307f48..209dd73 100644 --- a/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql +++ b/Bigfoot/Sources/Engine/BigFile/BigFileSchema.sql @@ -1,41 +1,38 @@ PRAGMA journal_mode=WAL; -PRAGMA foreign_keys = ON; -DROP TABLE IF EXISTS AssetHeader; -CREATE TABLE IF NOT EXISTS AssetHeader ( - UUID BLOB NOT NULL UNIQUE, - Name TEXT NOT NULL UNIQUE, - TypeID INTEGER NOT NULL, - TypeName TEXT NOT NULL, +CREATE TABLE IF NOT EXISTS AssetMetadata ( + UUID BLOB 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) -); +) WITHOUT ROWID; -CREATE TRIGGER IF NOT EXISTS AssetHeader_UpdateTime -AFTER UPDATE OF Name, TypeID, TypeName ON AssetHeader FOR EACH ROW -BEGIN - UPDATE AssetHeader - SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 - WHERE UUID = NEW.UUID; -END; - -DROP TABLE IF EXISTS Asset; CREATE TABLE IF NOT EXISTS Asset ( - UUID BLOB NOT NULL UNIQUE, + UUID BLOB NOT NULL, 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 +) WITHOUT ROWID; CREATE TRIGGER IF NOT EXISTS Asset_UpdateTime AFTER UPDATE OF Asset ON Asset FOR EACH ROW BEGIN - UPDATE AssetHeader - SET ModificationTime = CAST(unixepoch('subsec') AS INTEGER) * 1000000 + UPDATE AssetMetadata + SET ModificationTime = CAST(unixepoch('subsec') * 1000000 AS INTEGER) WHERE UUID = NEW.UUID; -END; \ No newline at end of file +END; + +CREATE TABLE IF NOT EXISTS AssetHardReference ( + UUID BLOB NOT NULL, + HardReference BLOB NOT NULL, + + PRIMARY KEY(UUID, HardReference), + FOREIGN KEY(UUID) REFERENCES AssetMetadata(UUID) + ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS AssetHardReference_ReferencedUUID ON AssetHardReference(HardReference); \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/CMakeLists.txt b/Bigfoot/Sources/Engine/CMakeLists.txt index 943596b..32ebc90 100644 --- a/Bigfoot/Sources/Engine/CMakeLists.txt +++ b/Bigfoot/Sources/Engine/CMakeLists.txt @@ -5,8 +5,7 @@ set(PublicDependencies SQLite::SQLite3) set(PrivateDependencies) set(BigfootPublicDependencies - System - Utils) + System) set(BigfootPrivateDependencies) set(BIGFOOT_MAJOR 0) diff --git a/Bigfoot/Sources/Engine/DataAccessor/DataAccessor.cpp b/Bigfoot/Sources/Engine/DataAccessor/DataAccessor.cpp new file mode 100644 index 0000000..28fd680 --- /dev/null +++ b/Bigfoot/Sources/Engine/DataAccessor/DataAccessor.cpp @@ -0,0 +1,15 @@ +/********************************************************************* + * \file DataAccessor.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +namespace Bigfoot +{ +void SetDataAccessor(DataAccessor* p_dataAccessor) +{ + g_dataAccessor = p_dataAccessor; +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.fbs b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.fbs new file mode 100644 index 0000000..efeff7a --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.fbs @@ -0,0 +1,21 @@ +include "Engine/Asset/AssetTypeID.fbs"; + +include "System/UUID/UUID.fbs"; + +namespace Flat.Bigfoot; + +table AssetHeader +{ + uuid: UUID (required, native_inline, id: 0); + name: string (required, id: 1); + type_id: AssetTypeID (required, native_inline, id: 2); + type_name: string (required, id: 3); +} + +table Asset +{ + header: AssetHeader (required, id: 0); + inner_asset: [ubyte] (required, id: 1); +} + +root_type Asset; \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.hpp new file mode 100644 index 0000000..09d0964 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset.hpp @@ -0,0 +1,214 @@ +/********************************************************************* + * \file Asset.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSET_HPP +#define BIGFOOT_ENGINE_ASSET_ASSET_HPP +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Bigfoot +{ +template + requires AssetTraits +class Asset: public AssetBase +{ + public: + using FLAT_ASSET = ASSET::FLAT; + + /** + * Constructor + * + * Will default initialize header + * + */ + Asset() + { + m_header.type_id = GetTypeID(); + m_header.type_name = eastl::string {GetTypeName()}; + } + + /** + * Get the header of the asset + * + * \return The header of the asset + */ + [[nodiscard]] + const ::Flat::Bigfoot::AssetHeaderT& GetHeader() const override + { + return m_header; + } + + /** + * Set the name of the asset + * + * \param p_name The name of the asset + */ + void SetName(const eastl::string_view p_name) + { + m_header.name = p_name; + } + + /** + * Get the asset + * + * \return The asset + */ + [[nodiscard]] + const FLAT_ASSET::NativeTableType& GetAsset() const + { + return m_asset; + } + + /** + * Get the asset + * + * \return The asset + */ + [[nodiscard]] + FLAT_ASSET::NativeTableType& GetAsset() + { + return m_asset; + } + + /** + * Unpack a flatbuffer to the asset + * + * \param p_flatbuffer The flatbuffer to unpack + */ + void Unpack(const eastl::span p_flatbuffer) override + { +#ifdef BIGFOOT_NOT_OPTIMIZED + flatbuffers::Verifier verifier {std::bit_cast(p_flatbuffer.data()), p_flatbuffer.size()}; + const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier); + CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!"); +#endif + + const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(p_flatbuffer.data()); + +#ifdef BIGFOOT_NOT_OPTIMIZED + flatbuffers::Verifier innerAssetVerifier {asset->inner_asset()->data(), asset->inner_asset()->size()}; + const bool innerAssetVerified = innerAssetVerifier.VerifyBuffer(); + CRITICAL_ASSERT(EngineAssertHandler, innerAssetVerified, "Given asset could not be verified!"); +#endif + + asset->header()->UnPackTo(&m_header); + flatbuffers::GetRoot(asset->inner_asset()->data())->UnPackTo(&m_asset, nullptr); + } + + /** + * Pack the asset to a flatbuffer + * + * \return The packed asset + */ + [[nodiscard]] + eastl::vector Pack() const override + { + flatbuffers::FlatBufferBuilder innerAssetFbb; + innerAssetFbb.Finish(FLAT_ASSET::Pack(innerAssetFbb, &m_asset)); + + flatbuffers::FlatBufferBuilder assetFbb; + const auto header = ::Flat::Bigfoot::AssetHeader::Pack(assetFbb, &m_header); + const auto innerAsset = assetFbb.CreateVector(innerAssetFbb.GetBufferPointer(), innerAssetFbb.GetSize()); + assetFbb.Finish(::Flat::Bigfoot::CreateAsset(assetFbb, header, innerAsset)); + + return eastl::vector {std::bit_cast(assetFbb.GetBufferPointer()), + std::bit_cast(assetFbb.GetBufferPointer() + assetFbb.GetSize())}; + } + + /** + * Get the HardReferences the flatbuffer has + * + * \param p_flatbuffer The flatbuffer + * \return The UUIDs of the HardReferences + */ + [[nodiscard]] + eastl::vector GetHardReferences(const eastl::span p_flatbuffer) const override + { +#ifdef BIGFOOT_NOT_OPTIMIZED + flatbuffers::Verifier verifier {std::bit_cast(p_flatbuffer.data()), p_flatbuffer.size()}; + const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier); + CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!"); +#endif + + const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(p_flatbuffer.data()); + +#ifdef BIGFOOT_NOT_OPTIMIZED + flatbuffers::Verifier innerAssetVerifier {asset->inner_asset()->data(), asset->inner_asset()->size()}; + const bool innerAssetVerified = innerAssetVerifier.VerifyBuffer(); + CRITICAL_ASSERT(EngineAssertHandler, innerAssetVerified, "Given asset could not be verified!"); +#endif + + HardReferenceVisitor visitor; + + flatbuffers::IterateFlatBuffer(asset->inner_asset()->data(), FLAT_ASSET::MiniReflectTypeTable(), &visitor); + return visitor.GetUUIDs(); + } + + /** + * Get the AssetTypeID + * + * \return The AssetTypeIPD + */ + [[nodiscard]] + static AssetTypeID GetTypeID() + { + static const AssetTypeID typeID = rapidhash(FLAT_ASSET::GetFullyQualifiedName(), + std::strlen(FLAT_ASSET::GetFullyQualifiedName())); + return typeID; + } + + /** + * Get the type name + * + * \return The type name + */ + [[nodiscard]] + static eastl::string_view GetTypeName() + { + return FLAT_ASSET::GetFullyQualifiedName(); + } + + private: + /** + * The asset + */ + FLAT_ASSET::NativeTableType m_asset; + /** + * The asset header + */ + ::Flat::Bigfoot::AssetHeaderT m_header; +}; + +/** + * Create an asset and insert it into its corresponding AssetContainer + * + * \tparam ASSET The Asset type + * \return The created asset, nullptr if no AssetContainer is registered for ASSET + */ +template + requires AssetType +[[nodiscard]] +ASSET* CreateAsset() +{ + AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(ASSET::GetTypeID()); + CRITICAL_ASSERT(EngineAssertHandler, container != nullptr, "No AssetContainer registered for this asset type!"); + + ContainerHandle* handle = container->Insert(); + return static_cast(handle->GetAsset()); +} +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetBase.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetBase.hpp new file mode 100644 index 0000000..661bb86 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetBase.hpp @@ -0,0 +1,111 @@ +/********************************************************************* + * \file AssetBase.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETBASE_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETBASE_HPP +#include + +#include + +#include +#include +#include +#include + +namespace Bigfoot +{ +class AssetBase +{ + public: + virtual ~AssetBase() = default; + + /** + * Get the header of the asset + * + * \return The header of the asset + */ + [[nodiscard]] + virtual const ::Flat::Bigfoot::AssetHeaderT& GetHeader() const = 0; + + /** + * Get the HardReferences the flatbuffer has + * + * \param p_flatbuffer The flatbuffer + * \return The UUIDs of the HardReferences + */ + [[nodiscard]] + virtual eastl::vector GetHardReferences(const eastl::span p_flatbuffer) const = 0; + + /** + * Unpack a flatbuffer to the asset + * + * \param p_flatbuffer The flatbuffer to unpack + */ + virtual void Unpack(const eastl::span p_flatbuffer) = 0; + + /** + * Pack the asset to a flatbuffer + * + * \return The packed asset + */ + [[nodiscard]] + virtual eastl::vector Pack() const = 0; + + AssetBase(const AssetBase& p_asset) = delete; + AssetBase& operator=(const AssetBase& p_asset) = delete; + + protected: + AssetBase() = default; + + AssetBase(AssetBase&& p_asset) = default; + + AssetBase& operator=(AssetBase&& p_asset) = default; + + class HardReferenceVisitor: public flatbuffers::IterationVisitor + { + public: + void Field(size_t p_fieldIndex, + size_t p_setIndex, + const flatbuffers::ElementaryType p_type, + const bool p_isVector, + const flatbuffers::TypeTable* p_typeTable, + const char* p_name, + const std::uint8_t* p_val) override; + + void Element(size_t p_index, + const flatbuffers::ElementaryType p_type, + const flatbuffers::TypeTable* p_typeTable, + const std::uint8_t* p_val) override; + + /** + * Get the visited HardReference + * + * \return The visited HardReferences + */ + [[nodiscard]] + eastl::vector GetUUIDs() const; + + private: + /** + * Visit an element in the flatbuffer + * + * \param p_type The element type + * \param p_typeTable The element typetable + * \param p_val The element + */ + void Visit(const flatbuffers::ElementaryType p_type, + const flatbuffers::TypeTable* p_typeTable, + const std::uint8_t* p_val); + + /** + * The HardReferences visited so far + */ + ankerl::unordered_dense::set m_uuids; + }; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetConcepts.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetConcepts.hpp new file mode 100644 index 0000000..6321e72 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetConcepts.hpp @@ -0,0 +1,43 @@ +/********************************************************************* + * \file AssetConcepts.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETCONCEPTS_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETCONCEPTS_HPP +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +class AssetBase; + +/** + * The traits struct passed to Asset (e.g. AssetATraits). + * Must name the generated flatbuffers table type for the asset. + */ +template +concept AssetTraits = requires { + typename TRAITS::FLAT; + typename TRAITS::FLAT::NativeTableType; + { TRAITS::FLAT::GetFullyQualifiedName() } -> std::convertible_to; + { TRAITS::FLAT::MiniReflectTypeTable() } -> std::convertible_to; +}; + +/** + * A concrete Asset type (e.g. AssetA), usable with AssetContainer, AssetLibrary and References. + */ +template +concept AssetType = std::derived_from && !std::same_as && + requires(const ASSET& p_asset) { + { ASSET::GetTypeID() } -> std::convertible_to; + { p_asset.GetHeader().uuid } -> std::convertible_to; + }; +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp new file mode 100644 index 0000000..beeaef0 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetContainer.hpp @@ -0,0 +1,364 @@ +/********************************************************************* + * \file AssetContainer.hpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETCONTAINER_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETCONTAINER_HPP +#include +#include +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +class AssetContainerBase; + +/** + * An indirection that allows References to immediately get the asset, without fetching the actual container + */ +class ContainerHandle +{ + public: + /** + * Constructor + * + * \param p_assetContainer The originating assetcontainer + * \param p_uuid The UUID of tha asset + */ + ContainerHandle(AssetContainerBase* p_assetContainer, const UUID p_uuid); + + /* + * Get the HardRef count + * + * \return The HardRef count. + */ + [[nodiscard]] + std::uint32_t GetHardRefCount() const; + + /* + * Increment the HardRef count + * + */ + void IncrementHardRefCount(); + + /* + * Decrement the HardRef count + * + */ + void DecrementHardRefCount(); + + /* + * Get the SoftRef count + * + * \return The SoftRef count. + */ + [[nodiscard]] + std::uint32_t GetSoftRefCount() const; + + /* + * Increment the SoftRef count + * + */ + void IncrementSoftRefCount(); + + /* + * Decrement the SoftRef count + * + */ + void DecrementSoftRefCount(); + + /* + * Get the Asset + * + * \return The asset, can be nullptr + */ + [[nodiscard]] + AssetBase* GetAsset(); + + /* + * Get the Asset + * + * \return The asset + */ + [[nodiscard]] + const AssetBase* GetAsset() const; + + /* + * Set the asset + * + * \param p_asset The asset + */ + void SetAsset(AssetBase* p_asset); + + private: + /* + * The HardRef count + */ + std::uint32_t m_hardRefCount = 0; + + /* + * The SoftRef count + */ + std::uint32_t m_softRefCount = 0; + + /* + * The asset + */ + AssetBase* m_asset = nullptr; + + /** + * The asset container + */ + AssetContainerBase* m_assetContainer = nullptr; + + /** + * The UUID of the asset + */ + UUID m_uuid = UUID::NULL_UUID; +}; + +/****************************************************************************************/ + +class AssetContainerBase +{ + using SlotMap = StableSlotMap; + using SlotMapKey = SlotMap::SlotMapKey; + + public: + virtual ~AssetContainerBase() = default; + + /* + * Inserts an Asset to the AssetContainer + * + * \param p_flatbuffer The flatbuffer to unpack to the asset + * \return The handle of the inserted asset + */ + [[nodiscard]] + virtual ContainerHandle* Insert(const eastl::span p_flatbuffer) = 0; + + /* + * Inserts an Asset to the AssetContainer + * + * \return The handle of the inserted asset + */ + [[nodiscard]] + virtual ContainerHandle* Insert() = 0; + + /* + * Check if an Asset is in the AssetContainer or not + * + * \param p_uuid The UUID to check + * \return True if in AssetContainer, false otherwise + */ + [[nodiscard]] + bool Has(const UUID& p_uuid) const; + + /* + * Acquires a ContainerHandle for a given UUID + * Will create it if it does not exists + * + * \param p_uuid The UUID to get ContainerHandle for + * \return A ContainerHandle for the UUID + */ + [[nodiscard]] + ContainerHandle* AcquireContainerHandle(const UUID& p_uuid); + + /* + * Remove an Asset from the AssetContainer + * + * \param p_uuid The UUID to remove + */ + void Remove(const UUID& p_uuid); + + /** + * Add an asset to be removed + * + * \param p_uuid UUID of the asset to remove + */ + void QueueForRemoval(const UUID& p_uuid); + + /** + * Remove an asset from the pending remove queue + * + * \param p_uuid UUID of the asset to cancel removal + */ + void CancelRemoval(const UUID& p_uuid); + + /** + * Process the assets that have been marked for remove + * + */ + void ProcessPendingRemovals(); + + [[nodiscard]] + bool IsMarkedForRemove(const UUID& p_uuid); + + AssetContainerBase& operator=(const AssetContainerBase& p_container) = delete; + AssetContainerBase& operator=(AssetContainerBase&& p_container) = delete; + + AssetContainerBase(const AssetContainerBase& p_container) = delete; + AssetContainerBase(AssetContainerBase&& p_container) = delete; + + protected: + struct Entry + { + /* + * SlotMapKey to the handle + */ + SlotMapKey m_handleSlotKey; + + /* + * SlotMapKey to the asset + */ + SlotMapKey m_assetSlotKey; + + /* + * The handle itself, cached to avoid a SlotMap lookup on every access + */ + ContainerHandle* m_handle = nullptr; + }; + + AssetContainerBase() = default; + + /* + * Finds an entry matching the Asset + * + * \param p_uuid The UUID to find + * \return Pointer to an Entry, nullptr otherwise + */ + [[nodiscard]] + Entry* FindEntry(const UUID& p_uuid); + + /* + * Finds an entry matching the Asset + * + * \param p_uuid The UUID to find + * \return Pointer to an Entry, nullptr otherwise + */ + [[nodiscard]] + const Entry* FindEntry(const UUID& p_uuid) const; + + /* + * Finds an entry matching the Asset + * Creates it if it does not exist + * + * \param p_uuid The UUID to find + * \return The entry matching the Asset + */ + [[nodiscard]] + Entry& GetOrCreateEntry(const UUID& p_uuid); + + /* + * Remove an asset + * + * \param p_key The key to the asset + */ + virtual void RemoveAsset(SlotMapKey p_slotKey) = 0; + + private: + /* + * HandleContainers + */ + SlotMap m_handles; + + /* + * Every Asset added to the map + */ + ankerl::unordered_dense::map m_uuidToEntry; + + /** + * Assets queued for removal + */ + eastl::vector m_pendingRemovalUuids; +}; + +/****************************************************************************************/ + +template + requires AssetType +class AssetContainer: public AssetContainerBase +{ + using SlotMap = StableSlotMap; + using SlotMapKey = SlotMap::SlotMapKey; + + public: + /* + * Inserts an Asset to the AssetContainer + * + * \param p_flatbuffer The flatbuffer to unpack to the asset + * \return The handle of the inserted asset + */ + [[nodiscard]] + ContainerHandle* Insert(const eastl::span p_flatbuffer) override + { + return InsertInternal(&p_flatbuffer); + } + + /* + * Inserts an Asset to the AssetContainer + * + * \return The handle of the inserted asset + */ + [[nodiscard]] + ContainerHandle* Insert() override + { + return InsertInternal(nullptr); + } + + private: + /* + * Inserts an Asset to the AssetContainer, unpacking it from a flatbuffer if given one + * + * \param p_flatbuffer The flatbuffer to unpack to the asset, nullptr to default-construct the asset + * \return The handle of the inserted asset + */ + [[nodiscard]] + ContainerHandle* InsertInternal(const eastl::span* p_flatbuffer) + { + SlotMapKey slotKey = m_assets.Insert(); + ASSET* asset = m_assets.Get(slotKey); + if (p_flatbuffer != nullptr) + { + asset->Unpack(*p_flatbuffer); + } + + Entry& entry = GetOrCreateEntry(asset->GetHeader().uuid); + if (entry.m_assetSlotKey.Valid()) + { + m_assets.Remove(slotKey); + slotKey = entry.m_assetSlotKey; + } + else + { + entry.m_assetSlotKey = slotKey; + entry.m_handle->SetAsset(asset); + } + + return entry.m_handle; + } + + /* + * Remove an asset + * + * \param p_key The key to the asset + */ + void RemoveAsset(SlotMapKey p_key) override + { + m_assets.Remove(p_key); + } + + /* + * The assets + */ + SlotMap m_assets; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetHelper.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetHelper.hpp new file mode 100644 index 0000000..e32fb9b --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetHelper.hpp @@ -0,0 +1,31 @@ +/********************************************************************* + * \file AssetHelper.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETHELPER_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETHELPER_HPP +#include +#include +#include + +#define BIGFOOT_ASSET(AssetName, AssetType) \ + namespace Bigfoot \ + { \ + template<> \ + AssetTypeID GetAssetTypeID() \ + { \ + return AssetType::GetTypeID(); \ + } \ + AssetContainer g_containerFor##AssetName; \ + const bool g_insertedContainerFor##AssetName = []() \ + { \ + const bool inserted = g_assetLibrary.Insert(&g_containerFor##AssetName); \ + CRITICAL_ASSERT(EngineAssertHandler, \ + inserted, \ + "AssetTypeID collision or duplicate registration for asset type '" #AssetName "'"); \ + return inserted; \ + }(); \ + } +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp new file mode 100644 index 0000000..91e7078 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetLibrary.hpp @@ -0,0 +1,154 @@ +/********************************************************************* + * \file AssetLibrary.hpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETLIBRARY_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETLIBRARY_HPP +#include +#include +#include + +#include + +namespace Bigfoot +{ +class AssetLibrary +{ + public: + AssetLibrary() = default; + + AssetLibrary(const AssetLibrary& p_library) = default; + AssetLibrary(AssetLibrary&& p_library) = delete; + + /** + * Insert an AssetContainer pointer to the library + * + * \tparam ASSET The Asset type + * \param p_assetContainer The asset container + * \return The AssetContainer for the Asset type. Nullptr if not found + */ + template + requires AssetType + [[nodiscard]] + bool Insert(AssetContainer* p_assetContainer) + { + static const AssetTypeID typeID = ASSET::GetTypeID(); + if (m_assetContainers.contains(typeID)) + { + return false; + } + + const auto inserted = m_assetContainers.emplace(typeID, p_assetContainer); + + return inserted.second; + } + + /** + * Get the AssetContainer for a given AssetType + * + * \param p_typeID The asset type ID + * \return The AssetContainer for the AssetType. Nullptr if not found + */ + [[nodiscard]] + AssetContainerBase* GetAssetContainer(const AssetTypeID p_typeID); + + /** + * Get the AssetContainer for a given AssetType + * + * \param p_typeID The asset type ID + * \return The AssetContainer for the AssetType. Nullptr if not found + */ + [[nodiscard]] + const AssetContainerBase* GetAssetContainer(const AssetTypeID p_typeID) const; + + /** + * Get the AssetContainer for a given UUID + * + * \param p_uuid The asset + * \return The AssetContainer for the UUID. Nullptr if not found + */ + [[nodiscard]] + const AssetContainerBase* GetAssetContainer(const UUID& p_uuid) const; + + /** + * Get the AssetContainer for a given UUID + * + * \param p_uuid The asset + * \return The AssetContainer for the UUID. Nullptr if not found + */ + [[nodiscard]] + AssetContainerBase* GetAssetContainer(const UUID& p_uuid); + + /** + * Check if an Asset is already loaded in the library + * + * \param p_uuid The asset to check + * \return True if already loaded, false otherwise + */ + [[nodiscard]] + bool IsLoaded(const UUID& p_uuid) const; + + /** + * Loop through the AssetContainers to remove the pending assets marked for removal + * + */ + void ProcessPendingRemovals(); + + AssetLibrary& operator=(const AssetLibrary& p_library) = default; + AssetLibrary& operator=(AssetLibrary&& p_library) = delete; + + ~AssetLibrary() = default; + + private: + // All the asset containers + ankerl::unordered_dense::map m_assetContainers; +}; + +/****************************************************************************************/ + +// The global asset library for Bigfoot +inline AssetLibrary g_assetLibrary; +inline AssetLibrary* g_activeAssetLibrary = &g_assetLibrary; + +/****************************************************************************************/ + +/* + * Set the active asset library + * + * \param p_assetLibrary The asset library to set g_activeAssetLibrary to. + */ +void SetActiveAssetLibrary(AssetLibrary* p_assetLibrary); + +/****************************************************************************************/ + +class ScopedActiveAssetLibrary +{ + public: + ScopedActiveAssetLibrary(AssetLibrary* p_assetLibrary): + m_previous(g_activeAssetLibrary) + { + SetActiveAssetLibrary(p_assetLibrary); + } + + ScopedActiveAssetLibrary(const ScopedActiveAssetLibrary& p_scopedActiveAssetLibrary) = delete; + ScopedActiveAssetLibrary(ScopedActiveAssetLibrary&& p_scopedActiveAssetLibrary) = delete; + + ~ScopedActiveAssetLibrary() + { + SetActiveAssetLibrary(m_previous); + } + + ScopedActiveAssetLibrary& operator=(const ScopedActiveAssetLibrary& p_scopedActiveAssetLibrary) = delete; + ScopedActiveAssetLibrary& operator=(ScopedActiveAssetLibrary&& p_scopedActiveAssetLibrary) = delete; + + private: + /* + * The previously active asset library. + */ + AssetLibrary* m_previous = nullptr; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.fbs b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.fbs new file mode 100644 index 0000000..6ffe9f6 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.fbs @@ -0,0 +1,8 @@ +native_include "Engine/Asset/AssetTypeID.hpp"; + +namespace Flat.Bigfoot; + +struct AssetTypeID (native_type: "::Bigfoot::AssetTypeID") +{ + value: ulong; +} \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.hpp new file mode 100644 index 0000000..65f0125 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID.hpp @@ -0,0 +1,31 @@ +/********************************************************************* + * \file AssetTypeID.hpp + * + * \author Romain BOULLARD + * \date February 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_ASSETTYPEID_HPP +#define BIGFOOT_ENGINE_ASSET_ASSETTYPEID_HPP +#include + +namespace Bigfoot +{ +using AssetTypeID = std::uint64_t; +} + +/****************************************************************************************/ + +namespace Flat::Bigfoot +{ +struct AssetTypeID; +} // namespace Flat::Bigfoot + +/****************************************************************************************/ + +namespace flatbuffers +{ +Flat::Bigfoot::AssetTypeID Pack(const Bigfoot::AssetTypeID& p_assetTypeID); +Bigfoot::AssetTypeID UnPack(const Flat::Bigfoot::AssetTypeID& p_flatAssetTypeID); +} // namespace flatbuffers + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID_generated.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID_generated.hpp new file mode 100644 index 0000000..d7b9b51 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/AssetTypeID_generated.hpp @@ -0,0 +1,78 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_ASSETTYPEID_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_ASSETTYPEID_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "Engine/Asset/AssetTypeID.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct AssetTypeID; + +inline const ::flatbuffers::TypeTable *AssetTypeIDTypeTable(); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) AssetTypeID FLATBUFFERS_FINAL_CLASS { + private: + uint64_t value_; + + public: + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetTypeIDTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetTypeID"; + } + AssetTypeID() + : value_(0) { + } + AssetTypeID(uint64_t _value) + : value_(::flatbuffers::EndianScalar(_value)) { + } + uint64_t value() const { + return ::flatbuffers::EndianScalar(value_); + } +}; +FLATBUFFERS_STRUCT_END(AssetTypeID, 8); + +struct AssetTypeID::Traits { + using type = AssetTypeID; +}; + +inline const ::flatbuffers::TypeTable *AssetTypeIDTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_ULONG, 0, -1 } + }; + static const int64_t values[] = { 0, 8 }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_STRUCT, 1, type_codes, nullptr, nullptr, values, nullptr + }; + return &tt; +} + +} // namespace Bigfoot +} // namespace Flat + +#endif // FLATBUFFERS_GENERATED_ASSETTYPEID_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset_generated.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset_generated.hpp new file mode 100644 index 0000000..d3ce995 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Asset_generated.hpp @@ -0,0 +1,398 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_ASSET_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_ASSET_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "Engine/Asset/AssetTypeID.hpp" +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/AssetTypeID_generated.hpp" +#include "System/UUID/UUID_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct AssetHeader; +struct AssetHeaderBuilder; +struct AssetHeaderT; + +struct Asset; +struct AssetBuilder; +struct AssetT; + +inline const ::flatbuffers::TypeTable *AssetHeaderTypeTable(); + +inline const ::flatbuffers::TypeTable *AssetTypeTable(); + +struct AssetHeaderT : public ::flatbuffers::NativeTable { + typedef AssetHeader TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetHeaderT"; + } + ::Bigfoot::UUID uuid{}; + eastl::string name{}; + ::Bigfoot::AssetTypeID type_id{}; + eastl::string type_name{}; +}; + +struct AssetHeader FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AssetHeaderT NativeTableType; + typedef AssetHeaderBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetHeaderTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetHeader"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_UUID = 4, + VT_NAME = 6, + VT_TYPE_ID = 8, + VT_TYPE_NAME = 10 + }; + const Flat::Bigfoot::UUID *uuid() const { + return GetStruct(VT_UUID); + } + const ::flatbuffers::String *name() const { + return GetPointer(VT_NAME); + } + const Flat::Bigfoot::AssetTypeID *type_id() const { + return GetStruct(VT_TYPE_ID); + } + const ::flatbuffers::String *type_name() const { + return GetPointer(VT_TYPE_NAME); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyFieldRequired(verifier, VT_UUID, 1) && + VerifyOffsetRequired(verifier, VT_NAME) && + verifier.VerifyString(name()) && + VerifyFieldRequired(verifier, VT_TYPE_ID, 8) && + VerifyOffsetRequired(verifier, VT_TYPE_NAME) && + verifier.VerifyString(type_name()) && + verifier.EndTable(); + } + AssetHeaderT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(AssetHeaderT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetHeaderT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct AssetHeaderBuilder { + typedef AssetHeader Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_uuid(const Flat::Bigfoot::UUID *uuid) { + fbb_.AddStruct(AssetHeader::VT_UUID, uuid); + } + void add_name(::flatbuffers::Offset<::flatbuffers::String> name) { + fbb_.AddOffset(AssetHeader::VT_NAME, name); + } + void add_type_id(const Flat::Bigfoot::AssetTypeID *type_id) { + fbb_.AddStruct(AssetHeader::VT_TYPE_ID, type_id); + } + void add_type_name(::flatbuffers::Offset<::flatbuffers::String> type_name) { + fbb_.AddOffset(AssetHeader::VT_TYPE_NAME, type_name); + } + explicit AssetHeaderBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + fbb_.Required(o, AssetHeader::VT_UUID); + fbb_.Required(o, AssetHeader::VT_NAME); + fbb_.Required(o, AssetHeader::VT_TYPE_ID); + fbb_.Required(o, AssetHeader::VT_TYPE_NAME); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAssetHeader( + ::flatbuffers::FlatBufferBuilder &_fbb, + const Flat::Bigfoot::UUID *uuid = nullptr, + ::flatbuffers::Offset<::flatbuffers::String> name = 0, + const Flat::Bigfoot::AssetTypeID *type_id = nullptr, + ::flatbuffers::Offset<::flatbuffers::String> type_name = 0) { + AssetHeaderBuilder builder_(_fbb); + builder_.add_type_name(type_name); + builder_.add_type_id(type_id); + builder_.add_name(name); + builder_.add_uuid(uuid); + return builder_.Finish(); +} + +struct AssetHeader::Traits { + using type = AssetHeader; + static auto constexpr Create = CreateAssetHeader; +}; + +::flatbuffers::Offset CreateAssetHeader(::flatbuffers::FlatBufferBuilder &_fbb, const AssetHeaderT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +struct AssetT : public ::flatbuffers::NativeTable { + typedef Asset TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetT"; + } + eastl::unique_ptr header{new Flat::Bigfoot::AssetHeaderT()}; + eastl::vector inner_asset{}; + AssetT() = default; + AssetT(const AssetT &o); + AssetT(AssetT&&) FLATBUFFERS_NOEXCEPT = default; + AssetT &operator=(AssetT o) FLATBUFFERS_NOEXCEPT; +}; + +struct Asset FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AssetT NativeTableType; + typedef AssetBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.Asset"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_HEADER = 4, + VT_INNER_ASSET = 6 + }; + const Flat::Bigfoot::AssetHeader *header() const { + return GetPointer(VT_HEADER); + } + const ::flatbuffers::Vector *inner_asset() const { + return GetPointer *>(VT_INNER_ASSET); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffsetRequired(verifier, VT_HEADER) && + verifier.VerifyTable(header()) && + VerifyOffsetRequired(verifier, VT_INNER_ASSET) && + verifier.VerifyVector(inner_asset()) && + verifier.EndTable(); + } + AssetT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(AssetT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct AssetBuilder { + typedef Asset Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_header(::flatbuffers::Offset header) { + fbb_.AddOffset(Asset::VT_HEADER, header); + } + void add_inner_asset(::flatbuffers::Offset<::flatbuffers::Vector> inner_asset) { + fbb_.AddOffset(Asset::VT_INNER_ASSET, inner_asset); + } + explicit AssetBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + fbb_.Required(o, Asset::VT_HEADER); + fbb_.Required(o, Asset::VT_INNER_ASSET); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAsset( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset header = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> inner_asset = 0) { + AssetBuilder builder_(_fbb); + builder_.add_inner_asset(inner_asset); + builder_.add_header(header); + return builder_.Finish(); +} + +struct Asset::Traits { + using type = Asset; + static auto constexpr Create = CreateAsset; +}; + +::flatbuffers::Offset CreateAsset(::flatbuffers::FlatBufferBuilder &_fbb, const AssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline AssetHeaderT *AssetHeader::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void AssetHeader::UnPackTo(AssetHeaderT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = uuid(); if (_e) _o->uuid = ::flatbuffers::UnPack(*_e); } + { auto _e = name(); if (_e) _o->name = eastl::string(_e->c_str(), _e->size()); } + { auto _e = type_id(); if (_e) _o->type_id = ::flatbuffers::UnPack(*_e); } + { auto _e = type_name(); if (_e) _o->type_name = eastl::string(_e->c_str(), _e->size()); } +} + +inline ::flatbuffers::Offset CreateAssetHeader(::flatbuffers::FlatBufferBuilder &_fbb, const AssetHeaderT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return AssetHeader::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset AssetHeader::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetHeaderT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const AssetHeaderT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _uuid = ::flatbuffers::Pack(_o->uuid); + auto _name = _fbb.CreateString(_o->name); + auto _type_id = ::flatbuffers::Pack(_o->type_id); + auto _type_name = _fbb.CreateString(_o->type_name); + return Flat::Bigfoot::CreateAssetHeader( + _fbb, + &_uuid, + _name, + &_type_id, + _type_name); +} + +inline AssetT::AssetT(const AssetT &o) + : header((o.header) ? new Flat::Bigfoot::AssetHeaderT(*o.header) : nullptr), + inner_asset(o.inner_asset) { +} + +inline AssetT &AssetT::operator=(AssetT o) FLATBUFFERS_NOEXCEPT { + std::swap(header, o.header); + std::swap(inner_asset, o.inner_asset); + return *this; +} + +inline AssetT *Asset::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void Asset::UnPackTo(AssetT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = header(); if (_e) { if(_o->header) { _e->UnPackTo(_o->header.get(), _resolver); } else { _o->header = eastl::unique_ptr(_e->UnPack(_resolver)); } } else if (_o->header) { _o->header.reset(); } } + { auto _e = inner_asset(); if (_e) { _o->inner_asset.resize(_e->size()); std::copy(_e->begin(), _e->end(), _o->inner_asset.begin()); } } +} + +inline ::flatbuffers::Offset CreateAsset(::flatbuffers::FlatBufferBuilder &_fbb, const AssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return Asset::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset Asset::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const AssetT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _header = _o->header ? CreateAssetHeader(_fbb, _o->header.get(), _rehasher) : 0; + auto _inner_asset = _fbb.CreateVector(_o->inner_asset.data(), _o->inner_asset.size()); + return Flat::Bigfoot::CreateAsset( + _fbb, + _header, + _inner_asset); +} + +inline const ::flatbuffers::TypeTable *AssetHeaderTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 }, + { ::flatbuffers::ET_STRING, 0, -1 }, + { ::flatbuffers::ET_SEQUENCE, 0, 1 }, + { ::flatbuffers::ET_STRING, 0, -1 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::UUIDTypeTable, + Flat::Bigfoot::AssetTypeIDTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 4, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const ::flatbuffers::TypeTable *AssetTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 }, + { ::flatbuffers::ET_UCHAR, 1, -1 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::AssetHeaderTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 2, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::Asset *GetAsset(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::Asset *GetSizePrefixedAsset(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyAssetBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedAssetBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *AssetExtension() { + return "bfbs"; +} + +inline void FinishAssetBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedAssetBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackAsset( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetAsset(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedAsset( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedAsset(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +#endif // FLATBUFFERS_GENERATED_ASSET_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.fbs b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.fbs new file mode 100644 index 0000000..1282b64 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.fbs @@ -0,0 +1,15 @@ +include "System/UUID/UUID.fbs"; + +native_include "Engine/Asset/Reference.hpp"; + +namespace Flat.Bigfoot; + +struct HardReference (bigfoot_ref_wrapper: "::Bigfoot::HardReference") +{ + uuid: UUID; +} + +struct SoftReference (bigfoot_ref_wrapper: "::Bigfoot::SoftReference") +{ + uuid: UUID; +} \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp new file mode 100644 index 0000000..e458112 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference.hpp @@ -0,0 +1,245 @@ +/********************************************************************* + * \file Reference.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_ASSET_REFERENCE_HPP +#define BIGFOOT_ENGINE_ASSET_REFERENCE_HPP +#include +#include + +#include + +#include + +namespace Bigfoot +{ +template +AssetTypeID GetAssetTypeID(); + +template<> +AssetTypeID GetAssetTypeID() = delete; + +template +class ReferenceBase +{ + public: + /** + * Get the UUID of the reference + * + * \return The UUID of the reference + */ + [[nodiscard]] + const UUID& GetUUID() const + { + return m_uuid; + } + + /** + * Get the asset + * + * \return The asset, nullptr if it is not loaded + */ + [[nodiscard]] + const ASSET* Get() const + { + static_assert(AssetType, "ASSET must derive from AssetBase (and not be AssetBase itself)"); + return m_handle ? static_cast(m_handle->GetAsset()) : nullptr; + } + + /** + * Get the asset + * + * \return The asset, nullptr if it is not loaded + */ + [[nodiscard]] + ASSET* Get() + { + static_assert(AssetType, "ASSET must derive from AssetBase (and not be AssetBase itself)"); + return m_handle ? static_cast(m_handle->GetAsset()) : nullptr; + } + + ~ReferenceBase() + { + Release(); + } + + ReferenceBase& operator=(const ReferenceBase& p_reference) + { + if (this != &p_reference) + { + Release(); + m_uuid = p_reference.m_uuid; + m_handle = p_reference.m_handle; + Acquire(); + } + return *this; + } + + ReferenceBase& operator=(ReferenceBase&& p_reference) noexcept + { + if (this != &p_reference) + { + Release(); + m_uuid = std::exchange(p_reference.m_uuid, UUID::NULL_UUID); + m_handle = std::exchange(p_reference.m_handle, nullptr); + } + return *this; + } + + [[nodiscard]] + bool operator==(const ReferenceBase& p_reference) const + { + return m_uuid == p_reference.m_uuid; + } + + private: + friend DERIVED; + + ReferenceBase() = default; + + ReferenceBase(const UUID& p_uuid): + m_uuid(p_uuid) + { + Acquire(); + } + + ReferenceBase(const ReferenceBase& p_reference): + m_uuid(p_reference.m_uuid), + m_handle(p_reference.m_handle) + { + Acquire(); + } + + ReferenceBase(ReferenceBase&& p_reference) noexcept: + m_uuid(std::exchange(p_reference.m_uuid, UUID::NULL_UUID)), + m_handle(std::exchange(p_reference.m_handle, nullptr)) + { + } + + /** + * Get the associated handle container for the asset + * Register the reference inside the handle + * + */ + void Acquire() + { + if (!m_uuid) + { + return; + } + + if (AssetContainerBase* assetContainer = g_activeAssetLibrary->GetAssetContainer(GetAssetTypeID())) + { + if (!m_handle) + { + m_handle = assetContainer->AcquireContainerHandle(m_uuid); + } + DERIVED::IncrementCount(*m_handle); + } + } + + /** + * Unregister the reference from the handle + * + */ + void Release() + { + if (m_handle) + { + DERIVED::DecrementCount(*m_handle); + m_handle = nullptr; + } + } + + /** + * The UUID + */ + UUID m_uuid = UUID::NULL_UUID; + + /** + * Handle for the asset + */ + ContainerHandle* m_handle = nullptr; +}; + +/* + * Owning reference + * Keeps the asset alive + */ +template +class HardReference: public ReferenceBase, ASSET> +{ + using Base = ReferenceBase, ASSET>; + + public: + HardReference() = default; + + HardReference(const UUID& p_uuid): + Base(p_uuid) + { + } + + private: + /** + * Increment the hard ref count of the handle + * + * \param p_handle The handle for the asset + */ + static void IncrementCount(ContainerHandle& p_handle) + { + p_handle.IncrementHardRefCount(); + } + + /** + * Decrement the hard ref count of the handle + * + * \param p_handle The handle for the asset + */ + static void DecrementCount(ContainerHandle& p_handle) + { + p_handle.DecrementHardRefCount(); + } + + friend Base; +}; + +template +class SoftReference: public ReferenceBase, ASSET> +{ + using Base = ReferenceBase, ASSET>; + + public: + SoftReference() = default; + + SoftReference(const UUID& p_uuid): + Base(p_uuid) + { + } + + private: + /** + * Increment the soft ref count of the handle + * + * \param p_handle The handle for the asset + */ + static void IncrementCount(ContainerHandle& p_handle) + { + p_handle.IncrementSoftRefCount(); + } + + /** + * Decrement the hard ref count of the handle + * + * \param p_handle The handle for the asset + */ + static void DecrementCount(ContainerHandle& p_handle) + { + p_handle.DecrementSoftRefCount(); + } + + friend Base; +}; +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference_generated.hpp b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference_generated.hpp new file mode 100644 index 0000000..1bc2051 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Asset/Reference_generated.hpp @@ -0,0 +1,129 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_REFERENCE_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_REFERENCE_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/Reference.hpp" +#include "System/UUID/UUID_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct HardReference; + +struct SoftReference; + +inline const ::flatbuffers::TypeTable *HardReferenceTypeTable(); + +inline const ::flatbuffers::TypeTable *SoftReferenceTypeTable(); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) HardReference FLATBUFFERS_FINAL_CLASS { + private: + Flat::Bigfoot::UUID uuid_; + + public: + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return HardReferenceTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.HardReference"; + } + HardReference() + : uuid_() { + } + HardReference(const Flat::Bigfoot::UUID &_uuid) + : uuid_(_uuid) { + } + const Flat::Bigfoot::UUID &uuid() const { + return uuid_; + } +}; +FLATBUFFERS_STRUCT_END(HardReference, 16); + +struct HardReference::Traits { + using type = HardReference; +}; + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) SoftReference FLATBUFFERS_FINAL_CLASS { + private: + Flat::Bigfoot::UUID uuid_; + + public: + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return SoftReferenceTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.SoftReference"; + } + SoftReference() + : uuid_() { + } + SoftReference(const Flat::Bigfoot::UUID &_uuid) + : uuid_(_uuid) { + } + const Flat::Bigfoot::UUID &uuid() const { + return uuid_; + } +}; +FLATBUFFERS_STRUCT_END(SoftReference, 16); + +struct SoftReference::Traits { + using type = SoftReference; +}; + +inline const ::flatbuffers::TypeTable *HardReferenceTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::UUIDTypeTable + }; + static const int64_t values[] = { 0, 16 }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_STRUCT, 1, type_codes, type_refs, nullptr, values, nullptr + }; + return &tt; +} + +inline const ::flatbuffers::TypeTable *SoftReferenceTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::UUIDTypeTable + }; + static const int64_t values[] = { 0, 16 }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_STRUCT, 1, type_codes, type_refs, nullptr, values, nullptr + }; + return &tt; +} + +} // namespace Bigfoot +} // namespace Flat + +#endif // FLATBUFFERS_GENERATED_REFERENCE_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp new file mode 100644 index 0000000..0ec41d2 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp @@ -0,0 +1,384 @@ +/********************************************************************* + * \file BigFile.hpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP +#define BIGFOOT_ENGINE_BIGFILE_BIGFILE_HPP +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_TOOLMODE +class BigFile: public DataAccessor +{ + public: + /** + * Constructor + * + * \param p_file The file targetting the BigFile DB + */ + BigFile(const File& p_file); + + BigFile(const BigFile& p_bigFile) = delete; + BigFile(BigFile&& p_bigFile) = delete; + + /** + * Begin a transaction + * + */ + void BeginTransaction(); + + /** + * Commit a transaction + * + */ + void CommitTransaction(); + + /** + * Rollback a transaction + * + */ + void RollbackTransaction(); + + /** + * Clear the BigFile + * + */ + void Clear(); + + ~BigFile() override; + + /** + * Get an Asset flatbuffer + * + * \param p_uuid The asset to get the flatbuffer of + * \return The flatbuffer, or an empty vector if the asset was not found + */ + [[nodiscard]] + eastl::vector 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 + */ + [[nodiscard]] + eastl::vector> GetAssetFlatbuffers(eastl::span p_uuids) const override; + + /** + * Get the HardReferences of a given asset + * + * \param p_uuid The asset to get the HardReferences of + * \return The HardReferences of the asset + */ + [[nodiscard]] + eastl::vector GetAssetHardReferences(const UUID& p_uuid) const override; + + /** + * Get the HardReferences of given assets + * + * \param p_uuids The assets to get the HardReferences of + * \return The HardReferences of the assets + */ + [[nodiscard]] + eastl::vector> GetAssetsHardReferences(eastl::span p_uuids) const override; + + struct AssetTime + { + Time m_creationTime; + Time m_modificationTime; + + [[nodiscard]] + bool operator==(const AssetTime& p_other) const = default; + }; + + /** + * Get the creation and modification time of an asset + * + * \param p_uuid The asset to get the time of + * \return The time of the asset, nullopt if the asset was not found + */ + [[nodiscard]] + eastl::optional GetAssetTime(const UUID& p_uuid) const; + + /** + * Get the creation and modification time of several assets + * + * \param p_uuids The assets to get the time of + * \return The time of the assets. nullopt for assets not found + */ + [[nodiscard]] + eastl::vector> GetAssetsTime(eastl::span p_uuids) const; + + /** + * Save an asset + * + * \param p_uuid The UUID + * \param p_flatbuffer The flatbuffer of the asset + * \param p_hardReferences The HardReferences the asset points to + */ + void SaveAssetFlatbuffer(const UUID& p_uuid, + const eastl::span p_flatbuffer, + const eastl::span p_hardReferences) override; + + /** + * Save several assets + * + * \param p_uuids The UUIDs + * \param p_flatbuffers The flatbuffers of the assets, matching p_metadatas by index + * \param p_hardReferences The HardReferences the asset points to + */ + void SaveAssetFlatbuffers(const eastl::span p_uuids, + const eastl::span> p_flatbuffers, + const eastl::span> p_hardReferences) override; + + /** + * Check if the Asset exists + * + * \param p_uuid The asset to check + * \param True if it exists, false otherwise + */ + [[nodiscard]] + bool Has(const UUID& p_uuid) const override; + + BigFile& operator=(const BigFile& p_bigFile) = delete; + BigFile& operator=(BigFile&& p_bigFile) = delete; + + class Request + { + public: + /* + * Constructor + * + * \param p_bigFile The bigfile + * \param p_request The SQL request + */ + Request(const BigFile& p_bigFile, const eastl::string_view p_request); + + Request(const Request& p_request) = delete; + Request(Request&& p_request) = delete; + + using CopyValue = TaggedType; + + /* + * Bind a int32 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::int32_t p_value); + + /* + * Bind a uint32 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::uint32_t p_value); + + /* + * Bind a int64 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::int64_t p_value); + + /* + * Bind a uint64 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::uint64_t p_value); + + /* + * Bind a float value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const float p_value); + + /* + * Bind a double value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const double p_value); + + /* + * Bind a string value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + * \param p_copy Should the memory be copied so that caller does not have to retain it + */ + void Bind(const std::uint32_t p_index, + const eastl::string_view p_value, + const CopyValue p_copy = CopyValue {false}); + + /* + * Bind a blob value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + * \param p_copy Should the memory be copied so that caller does not have to retain it + */ + void Bind(const std::uint32_t p_index, + const eastl::span p_value, + const CopyValue p_copy = CopyValue {false}); + + /* + * Step through the request + * + * \return True if a row is available to read via Get(), false once the statement is done + */ + [[nodiscard]] + bool Step(); + + /* + * Execute the request once + * + * \return The number of rows modified by the statement + */ + [[maybe_unused]] + std::uint32_t Execute(); + + class Column + { + public: + /* + * Constructor + * + * \param p_request The request to get the column of + * \param p_index Column index + */ + Column(const Request& p_request, const std::uint32_t p_index); + + /* + * Get value as a int32 + * + * \return The value + */ + operator std::int32_t() const; + + /* + * Get value as a uint32 + * + * \return The value + */ + operator std::uint32_t() const; + + /* + * Get value as a int64 + * + * \return The value + */ + operator std::int64_t() const; + + /* + * Get value as a uint64 + * + * \return The value + */ + operator std::uint64_t() const; + + /* + * Get value as a float + * + * \return The value + */ + operator float() const; + + /* + * Get value as a double + * + * \return The value + */ + operator double() const; + + /* + * Get value as a string + * + * \return The value + */ + operator eastl::string_view() const; + + /* + * Get value as a blob + * + * \return The value + */ + operator eastl::span() const; + + /* + * Get value as a blob + * + * \return The value + */ + operator std::span() const; + + private: + /* + * The statement + */ + sqlite3_stmt* m_statement; + + /* + * The column index + */ + std::uint32_t m_index; + }; + + /* + * Get a column + * + * \param p_index Column index + */ + [[nodiscard]] + Column Get(const std::uint32_t p_index) const; + + ~Request(); + + Request& operator=(const Request& p_request) = delete; + Request& operator=(Request&& p_request) = delete; + + private: + /* + * The database + */ + sqlite3* m_db; + + /* + * The statement + */ + sqlite3_stmt* m_statement = nullptr; + }; + + private: + /** + * The BigFile DB + */ + sqlite3* m_db = nullptr; +}; +#endif +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in index 66f4d7e..44e8ffb 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in +++ b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in @@ -9,6 +9,8 @@ #include +#if defined BIGFOOT_TOOLMODE + namespace Bigfoot { /* @@ -16,4 +18,5 @@ namespace Bigfoot */ constexpr eastl::string_view BIGFILE_@BIGFILE_NAME@_LOCATION {"@BIGFILE_LOCATION@"}; } // namespace Bigfoot +#endif #endif \ No newline at end of file diff --git a/Bigfoot/Sources/Engine/Include/Engine/DataAccessor/DataAccessor.hpp b/Bigfoot/Sources/Engine/Include/Engine/DataAccessor/DataAccessor.hpp new file mode 100644 index 0000000..a9ddf40 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/DataAccessor/DataAccessor.hpp @@ -0,0 +1,145 @@ +/********************************************************************* + * \file DataAccessor.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_DATAACCESSOR_DATAACCESSOR_HPP +#define BIGFOOT_ENGINE_DATAACCESSOR_DATAACCESSOR_HPP +#include + +#include +#include + +#include +#include +#include + +namespace Bigfoot +{ +class DataAccessor +{ + public: + DataAccessor() = default; + + DataAccessor(const DataAccessor& p_accessor) = default; + DataAccessor(DataAccessor&& p_accessor) = default; + + virtual ~DataAccessor() = default; + + /** + * Get an Asset flatbuffer + * + * \param p_uuid The asset to get the flatbuffer of + * \return The flatbuffer, or an empty vector if the asset was not found + */ + [[nodiscard]] + virtual eastl::vector GetAssetFlatbuffer(const UUID& p_uuid) const = 0; + + /** + * Get several Asset flatbuffers + * + * \param p_uuids The assets to get the flatbuffers of + * \return The flatbuffers that were found + */ + [[nodiscard]] + virtual eastl::vector> GetAssetFlatbuffers(eastl::span p_uuids) const = 0; + + /** + * Get the HardReferences of a given asset + * + * \param p_uuid The asset to get the HardReferences of + * \return The HardReferences of the asset + */ + [[nodiscard]] + virtual eastl::vector GetAssetHardReferences(const UUID& p_uuid) const = 0; + + /** + * Get the HardReferences of given assets + * + * \param p_uuids The assets to get the HardReferences of + * \return The HardReferences of the assets + */ + [[nodiscard]] + virtual eastl::vector> GetAssetsHardReferences(eastl::span p_uuids) const = 0; + + /** + * Save an asset + * + * \param p_uuid The UUID + * \param p_flatbuffer The flatbuffer of the asset + * \param p_hardReferences The HardReferences the asset points to + */ + virtual void SaveAssetFlatbuffer(const UUID& p_uuid, + const eastl::span p_flatbuffer, + const eastl::span p_hardReferences) = 0; + + /** + * Save several assets + * + * \param p_uuids The UUIDs + * \param p_flatbuffers The flatbuffers of the assets, matching p_metadatas by index + * \param p_hardReferences The HardReferences the asset points to + */ + virtual void SaveAssetFlatbuffers(const eastl::span p_uuids, + const eastl::span> p_flatbuffers, + const eastl::span> p_hardReferences) = 0; + + /** + * Check if the Asset exists + * + * \param p_uuid The asset to check + * \param True if it exists, false otherwise + */ + [[nodiscard]] + virtual bool Has(const UUID& p_uuid) const = 0; + + DataAccessor& operator=(const DataAccessor& p_accessor) = default; + DataAccessor& operator=(DataAccessor&& p_accessor) = default; +}; + +/****************************************************************************************/ + +// The global data accessor for Bigfoot +inline DataAccessor* g_dataAccessor = nullptr; + +/****************************************************************************************/ + +/* + * Set the data accessor + * + * \param p_dataAccessor The data accessor to set g_dataAccessor to. + */ +void SetDataAccessor(DataAccessor* p_dataAccessor); + +/****************************************************************************************/ + +class ScopedDataAccessor +{ + public: + ScopedDataAccessor(DataAccessor* p_dataAccessor): + m_previous(g_dataAccessor) + { + SetDataAccessor(p_dataAccessor); + } + + ScopedDataAccessor(const ScopedDataAccessor& p_scopedDataAccessor) = delete; + ScopedDataAccessor(ScopedDataAccessor&& p_scopedDataAccessor) = delete; + + ~ScopedDataAccessor() + { + SetDataAccessor(m_previous); + } + + ScopedDataAccessor& operator=(const ScopedDataAccessor& p_scopedDataAccessor) = delete; + ScopedDataAccessor& operator=(ScopedDataAccessor&& p_scopedDataAccessor) = delete; + + private: + /* + * The previous data accessor. + */ + DataAccessor* m_previous = nullptr; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/EngineAssertHandler.hpp b/Bigfoot/Sources/Engine/Include/Engine/EngineAssertHandler.hpp index 1c969a7..f16a3a3 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/EngineAssertHandler.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/EngineAssertHandler.hpp @@ -6,55 +6,15 @@ *********************************************************************/ #ifndef BIGFOOT_ENGINE_ENGINEASSERTHANDLER_HPP #define BIGFOOT_ENGINE_ENGINEASSERTHANDLER_HPP -#include - #include -#if defined BIGFOOT_NOT_OPTIMIZED -#include +#include -#include -#include -#include +#ifdef BIGFOOT_NOT_OPTIMIZED namespace Bigfoot { -class EngineAssertHandler -{ - public: - EngineAssertHandler() = delete; - - EngineAssertHandler(const EngineAssertHandler& p_handler) = delete; - EngineAssertHandler(EngineAssertHandler&& p_handler) = delete; - - ~EngineAssertHandler() = delete; - - /** - * Handle an assertion. - * - * \param p_location Location of the assertion. - * \param p_stacktrace The stack trace - * \param p_format Format string for the assertion message. - * \param p_args Arguments for the format string. - */ - template - static void Handle(const std::source_location& p_location, - const std::string_view p_stacktrace, - std::format_string p_format, - ARGS&&... p_args) - { - BIGFOOT_LOG_FATAL(ENGINE_LOGGER, - "Assert: {} (File:{}, Line:{}, Function:{}\n{}", - std::format(p_format, eastl::forward(p_args)...), - p_location.file_name(), - p_location.line(), - p_location.function_name(), - p_stacktrace); - } - - EngineAssertHandler& operator=(const EngineAssertHandler& p_handler) = delete; - EngineAssertHandler& operator=(EngineAssertHandler&& p_handler) = delete; -}; +using EngineAssertHandler = AssertHandler; } // namespace Bigfoot #endif #endif diff --git a/Bigfoot/Sources/Engine/Include/Engine/EngineLogger_generated.hpp b/Bigfoot/Sources/Engine/Include/Engine/EngineLogger_generated.hpp index e3e58e9..0067c8c 100644 --- a/Bigfoot/Sources/Engine/Include/Engine/EngineLogger_generated.hpp +++ b/Bigfoot/Sources/Engine/Include/Engine/EngineLogger_generated.hpp @@ -6,7 +6,7 @@ *********************************************************************/ #ifndef BIGFOOT_ENGINELOGGER_GENERATED_HPP #define BIGFOOT_ENGINELOGGER_GENERATED_HPP -#include +#include #if defined BIGFOOT_NOT_OPTIMIZED 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..734e5bd --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/Sasquatch/Sasquatch.hpp @@ -0,0 +1,243 @@ +/********************************************************************* + * \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(std::is_trivially_copyable_v); + +/****************************************************************************************/ + +struct SasquatchEntry +{ + std::array m_uuid {}; + + std::size_t m_assetOffset = 0; + std::size_t m_assetSize = 0; + + std::size_t m_hardReferencesOffset = 0; + std::size_t m_hardReferencesSize = 0; +}; + +static_assert(std::is_trivially_copyable_v); + +/****************************************************************************************/ + +#if defined(BIGFOOT_TOOLMODE) || defined(BIGFOOT_BUILD_TESTS) || defined(BIGFOOT_BUILD_BENCHMARKS) +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 + * \param p_totalHardReferencesBytes The sum of the sizes of every asset hardreferences that will be written + */ + SasquatchWriter(const File& p_file, + const std::uint64_t p_assetCount, + const std::size_t p_totalFlatbufferBytes, + const std::size_t p_totalHardReferencesBytes); + + 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, + eastl::span p_hardReferences); + + SasquatchWriter& operator=(const SasquatchWriter& p_writer) = delete; + SasquatchWriter& operator=(SasquatchWriter&& p_writer) = delete; + + private: + static constexpr std::uint64_t PREFETCH_BYTES = 4ULL * 1024 * 1024; + + /** + * The number of assets expected + */ + std::uint64_t m_assetCount; + + /** + * How far into m_file has already been prefetched + */ + std::size_t m_prefetchedUpTo = 0; + + /** + * Current write position in the data region + */ + std::size_t m_currentDataOffset; + + /** + * The Sasquatch file being written, mapped at its final size + */ + WritableMemoryMappedFile m_file; + + /** + * The entries + */ + eastl::vector m_entries; +}; + +/****************************************************************************************/ + +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 the header + * + * \return The header + */ + [[nodiscard]] + const SasquatchHeader& GetHeader() const; + + /** + * Get an Asset flatbuffer + * + * \param p_uuid The asset to get the flatbuffer of + * \return The flatbuffer, or an empty vector if the asset was not found + */ + [[nodiscard]] + eastl::vector 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 + */ + [[nodiscard]] + eastl::vector> GetAssetFlatbuffers(eastl::span p_uuids) const override; + + /** + * Get the HardReferences of a given asset + * + * \param p_uuid The asset to get the HardReferences of + * \return The HardReferences of the asset + */ + [[nodiscard]] + eastl::vector GetAssetHardReferences(const UUID& p_uuid) const override; + + /** + * Get the HardReferences of given assets + * + * \param p_uuids The assets to get the HardReferences of + * \return The HardReferences of the assets + */ + [[nodiscard]] + eastl::vector> GetAssetsHardReferences(eastl::span p_uuids) const override; + + /** + * Sasquatch is read-only: this will always fail + * + */ + void SaveAssetFlatbuffer(const UUID& p_uuid, + eastl::span p_flatbuffer, + eastl::span p_hardReferences) override; + + /** + * Sasquatch is read-only: this will always fail + * + */ + void SaveAssetFlatbuffers(const eastl::span p_uuids, + eastl::span> p_flatbuffers, + eastl::span> p_hardReferences) override; + + /** + * Check if the Asset exists + * + * \param p_uuid The asset to check + * \param True if it exists, false otherwise + */ + [[nodiscard]] + 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_assetOffset = 0; + std::size_t m_assetSize = 0; + + std::size_t m_hardReferencesOffset = 0; + std::size_t m_hardReferencesSize = 0; + }; + + /** + * The Sasquatch file + */ + MemoryMappedFile m_file; + + /** + * The sasquatch header + */ + SasquatchHeader m_header; + + /** + * 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/Include/Engine/SerializationManager/SerializationManager.hpp b/Bigfoot/Sources/Engine/Include/Engine/SerializationManager/SerializationManager.hpp new file mode 100644 index 0000000..9ebec1d --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/SerializationManager/SerializationManager.hpp @@ -0,0 +1,54 @@ +/********************************************************************* + * \file SerializationManager.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_SERIALIZATIONMANAGER_SERIALIZATIONMANAGER_HPP +#define BIGFOOT_ENGINE_SERIALIZATIONMANAGER_SERIALIZATIONMANAGER_HPP +#include + +#include + +namespace Bigfoot::SerializationManager +{ +/** + * Load an Asset, along with every HardReference it transitively points to + * Already loaded assets are skipped. Assets that are part of a cyclic dependency are left unloaded + * + * \param p_uuid The asset to load + */ +void LoadAsset(const UUID& p_uuid); + +/** + * Load several Assets, along with every HardReference they transitively point to + * Already loaded assets are skipped. Assets that are part of a cyclic dependency are left unloaded + * + * \param p_uuids The assets to load + */ +void LoadAssets(const eastl::span p_uuids); + +#ifdef BIGFOOT_TOOLMODE +/** + * Save an Asset + * Fails without saving if a cyclic dependency is detected starting from this asset + * + * \param p_uuid The asset to save + * \return True if the asset was saved, false otherwise + */ +[[nodiscard]] +bool SaveAsset(const UUID& p_uuid); + +/** + * Save several Assets + * Fails without saving any of them if a cyclic dependency is detected starting from any of them + * + * \param p_uuids The assets to save + * \return True if the assets were saved, false otherwise + */ +[[nodiscard]] +bool SaveAssets(const eastl::span p_uuids); +#endif +} // namespace Bigfoot::SerializationManager + +#endif diff --git a/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp b/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp new file mode 100644 index 0000000..dc0632e --- /dev/null +++ b/Bigfoot/Sources/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,266 @@ +/********************************************************************* + * \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, + const std::size_t p_totalHardReferencesBytes): + m_assetCount(p_assetCount), + m_currentDataOffset(sizeof(SasquatchHeader) + (m_assetCount * sizeof(SasquatchEntry))), + m_file(p_file, + sizeof(SasquatchHeader) + (m_assetCount * sizeof(SasquatchEntry)) + p_totalFlatbufferBytes + + p_totalHardReferencesBytes) +{ + m_entries.reserve(m_assetCount); + + SasquatchHeader header; + header.m_assetCount = m_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)); + + if (!m_entries.empty()) + { + std::memcpy(m_file.GetData().data() + sizeof(SasquatchHeader), + m_entries.data(), + m_entries.size() * sizeof(SasquatchEntry)); + } +} + +/****************************************************************************************/ + +void SasquatchWriter::WriteAsset(const UUID& p_uuid, + eastl::span p_flatbuffer, + eastl::span p_hardReferences) +{ + ASSERT(EngineAssertHandler, + m_entries.size() < m_assetCount, + "SasquatchWriter can only hold {} assets", + m_assetCount); + + const std::size_t offset = m_currentDataOffset; + + const eastl::span data = m_file.GetData(); + ASSERT(EngineAssertHandler, + offset + p_flatbuffer.size() + (p_hardReferences.size() * UUID::UUID_BYTE_SIZE) <= data.size(), + "SasquatchWriter can only write {} bytes", + data.size()); + + const std::size_t writeEnd = offset + p_flatbuffer.size() + (p_hardReferences.size() * UUID::UUID_BYTE_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_assetOffset = offset; + entry.m_assetSize = p_flatbuffer.size(); + entry.m_hardReferencesOffset = entry.m_assetOffset + entry.m_assetSize; + entry.m_hardReferencesSize = p_hardReferences.size() * UUID::UUID_BYTE_SIZE; + + std::memcpy(data.data() + entry.m_assetOffset, p_flatbuffer.data(), entry.m_assetSize); + + for (std::uint32_t i = 0; i < p_hardReferences.size(); ++i) + { + const std::span rawUUID = p_hardReferences[i].ToBytes(); + std::memcpy(data.data() + entry.m_hardReferencesOffset + (static_cast(i) * UUID::UUID_BYTE_SIZE), + rawUUID.data(), + UUID::UUID_BYTE_SIZE); + } + + m_currentDataOffset += entry.m_assetSize + entry.m_hardReferencesSize; + m_entries.push_back(entry); +} + +/****************************************************************************************/ + +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()); + + std::memcpy(&m_header, data.data(), sizeof(SasquatchHeader)); + + CRITICAL_ASSERT(EngineAssertHandler, + m_header.m_magic == SasquatchHeader::MAGIC, + "Sasquatch file {} has an invalid magic number (got {} / expected {})", + p_file.GetPath(), + m_header.m_magic, + SasquatchHeader::MAGIC); + CRITICAL_ASSERT(EngineAssertHandler, + m_header.m_version == SasquatchHeader::VERSION, + "Sasquatch file {} has an unsupported version (got {} / expected {})", + p_file.GetPath(), + m_header.m_version, + SasquatchHeader::VERSION); + + CRITICAL_ASSERT(EngineAssertHandler, + data.size() >= sizeof(SasquatchHeader) + (m_header.m_assetCount * sizeof(SasquatchEntry)), + "Sasquatch file {} index is truncated (got {} / expected at least {})", + p_file.GetPath(), + data.size(), + sizeof(SasquatchHeader) + (m_header.m_assetCount * sizeof(SasquatchEntry))); + + eastl::vector entries(m_header.m_assetCount); + if (!entries.empty()) + { + std::memcpy(entries.data(), data.data() + sizeof(SasquatchHeader), entries.size() * sizeof(SasquatchEntry)); + } + + m_entries.reserve(m_header.m_assetCount); + for (const SasquatchEntry& entry: entries) + { + CRITICAL_ASSERT(EngineAssertHandler, + entry.m_assetOffset + entry.m_assetSize + entry.m_hardReferencesSize <= data.size(), + "Sasquatch file {} has an out-of-bounds asset entry", + p_file.GetPath()); + + m_entries.emplace(UUID {entry.m_uuid}, + Entry {.m_assetOffset = entry.m_assetOffset, + .m_assetSize = entry.m_assetSize, + .m_hardReferencesOffset = entry.m_hardReferencesOffset, + .m_hardReferencesSize = entry.m_hardReferencesSize}); + } +} + +/****************************************************************************************/ + +const SasquatchHeader& Sasquatch::GetHeader() const +{ + return m_header; +} + +/****************************************************************************************/ + +eastl::vector Sasquatch::GetAssetFlatbuffer(const UUID& p_uuid) const +{ + const auto entryIt = m_entries.find(p_uuid); + if (entryIt == m_entries.end()) + { + return {}; + } + + const eastl::span data = m_file.GetData(); + const eastl::span bytes {data.data() + entryIt->second.m_assetOffset, + static_cast(entryIt->second.m_assetSize)}; + return eastl::vector {bytes.begin(), bytes.end()}; +} + +/****************************************************************************************/ + +eastl::vector> Sasquatch::GetAssetFlatbuffers(const eastl::span p_uuids) const +{ + eastl::vector> flatbuffers; + flatbuffers.reserve(p_uuids.size()); + + for (const UUID& uuid: p_uuids) + { + flatbuffers.push_back(GetAssetFlatbuffer(uuid)); + } + + return flatbuffers; +} + +/****************************************************************************************/ + +eastl::vector Sasquatch::GetAssetHardReferences(const UUID& p_uuid) const +{ + eastl::vector hardReferences; + + const auto entryIt = m_entries.find(p_uuid); + if (entryIt == m_entries.end()) + { + return hardReferences; + } + + const eastl::span data = m_file.GetData(); + const eastl::span bytes {data.data() + entryIt->second.m_hardReferencesOffset, + static_cast(entryIt->second.m_hardReferencesSize)}; + + const std::size_t hardReferenceCount = bytes.size() / UUID::UUID_BYTE_SIZE; + hardReferences.reserve(hardReferenceCount); + + for (std::size_t i = 0; i < hardReferenceCount; ++i) + { + std::array rawUUID; + std::memcpy(rawUUID.data(), bytes.data() + (i * UUID::UUID_BYTE_SIZE), UUID::UUID_BYTE_SIZE); + hardReferences.emplace_back(rawUUID); + } + + return hardReferences; +} + +/****************************************************************************************/ + +eastl::vector> Sasquatch::GetAssetsHardReferences(eastl::span p_uuids) const +{ + eastl::vector> hardReferences; + hardReferences.reserve(p_uuids.size()); + + for (const UUID& uuid: p_uuids) + { + hardReferences.push_back(GetAssetHardReferences(uuid)); + } + + return hardReferences; +} + +/****************************************************************************************/ + +void Sasquatch::SaveAssetFlatbuffer([[maybe_unused]] const UUID& p_uuid, + [[maybe_unused]] eastl::span p_flatbuffer, + [[maybe_unused]] eastl::span p_hardReferences) +{ + ASSERT(EngineAssertHandler, false, "Sasquatch is read-only, it cannot save assets"); +} + +/****************************************************************************************/ + +void Sasquatch::SaveAssetFlatbuffers([[maybe_unused]] const eastl::span p_uuids, + [[maybe_unused]] eastl::span> p_flatbuffers, + [[maybe_unused]] eastl::span> p_hardReferences) +{ + ASSERT(EngineAssertHandler, false, "Sasquatch is read-only, it cannot save assets"); +} + +/****************************************************************************************/ + +bool Sasquatch::Has(const UUID& p_uuid) const +{ + return m_entries.contains(p_uuid); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/SerializationManager/SerializationManager.cpp b/Bigfoot/Sources/Engine/SerializationManager/SerializationManager.cpp new file mode 100644 index 0000000..5a830fa --- /dev/null +++ b/Bigfoot/Sources/Engine/SerializationManager/SerializationManager.cpp @@ -0,0 +1,248 @@ +/********************************************************************* + * \file SerializationManager.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include + +namespace Bigfoot::SerializationManager +{ +/* + * Build the transitive HardReference graph reachable from p_roots + * + * \param p_roots The assets to start the discovery from + * \param p_discovered Every discovered asset that isn't already loaded, including p_roots + * \return Every asset's HardReferences + */ +[[nodiscard]] +ankerl::unordered_dense::map> BuildDependencyGraph( + const eastl::span p_roots, + ankerl::unordered_dense::set& p_discovered) +{ + ankerl::unordered_dense::map> graph; + + eastl::vector toQuery; + toQuery.reserve(p_roots.size()); + for (const UUID& uuid: p_roots) + { + if (!g_activeAssetLibrary->IsLoaded(uuid) && p_discovered.insert(uuid).second) + { + toQuery.push_back(uuid); + } + } + + while (!toQuery.empty()) + { + eastl::vector> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery); + + eastl::vector nextToQuery; + for (std::size_t i = 0; i < toQuery.size(); ++i) + { + eastl::vector& hardReferences = graph[toQuery[i]]; + hardReferences = std::move(hardReferencesBatch[i]); + + for (const UUID& hardReference: hardReferences) + { + if (!g_activeAssetLibrary->IsLoaded(hardReference) && p_discovered.insert(hardReference).second) + { + nextToQuery.push_back(hardReference); + } + } + } + + toQuery = std::move(nextToQuery); + } + + return graph; +} + +/****************************************************************************************/ + +void LoadAssets(const eastl::span p_uuids) +{ + ankerl::unordered_dense::set discovered; + const ankerl::unordered_dense::map> graph = BuildDependencyGraph(p_uuids, discovered); + + // Assets that are part of a cyclic HardReference dependency are left out of waves and simply never loaded. + eastl::vector> waves; + eastl::vector cyclicUuids; + std::ignore = LayeredTopologicalSort(graph, discovered, waves, cyclicUuids); + BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(), + ENGINE_LOGGER, + "Cyclic HardReference dependency detected while loading assets, involved UUIDs: {}", + cyclicUuids); + + // Waves are loaded one after another since a wave's assets may HardReference the previous wave's; within a + // wave, every asset is independent of the others, so this is where loading could later be parallelized. + for (const eastl::vector& wave: waves) + { + const eastl::vector> assetFlatbuffers = g_dataAccessor->GetAssetFlatbuffers(wave); + for (std::size_t i = 0; i < wave.size(); ++i) + { + const eastl::vector& flatbuffer = assetFlatbuffers[i]; + if (flatbuffer.empty()) + { + BIGFOOT_LOG_ERROR(ENGINE_LOGGER, "Asset {} could not be found while loading, skipping it", wave[i]); + continue; + } + +#ifdef BIGFOOT_NOT_OPTIMIZED + flatbuffers::Verifier verifier {std::bit_cast(flatbuffer.data()), flatbuffer.size()}; + const bool assetVerified = ::Flat::Bigfoot::VerifyAssetBuffer(verifier); + CRITICAL_ASSERT(EngineAssertHandler, assetVerified, "Given asset could not be verified!"); +#endif + + const ::Flat::Bigfoot::Asset* asset = flatbuffers::GetRoot<::Flat::Bigfoot::Asset>(flatbuffer.data()); + + AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer( + flatbuffers::UnPack(*asset->header()->type_id())); + CRITICAL_ASSERT(EngineAssertHandler, + container != nullptr, + "No AssetContainer registered for asset {}", + wave[i]); + std::ignore = container->Insert(flatbuffer); + } + } +} + +/****************************************************************************************/ + +void LoadAsset(const UUID& p_uuid) +{ + LoadAssets(eastl::span {&p_uuid, 1}); +} + +/****************************************************************************************/ + +#ifdef BIGFOOT_TOOLMODE +/* + * Finds the cyclic dependencies that may exist in Assets + * + * \param p_uuids The assets we want to explore + * \param p_hardReferences The HardReferences of p_uuids, matching by index + * \return Every UUID caught in a cyclic dependency + */ +[[nodiscard]] +eastl::vector FindCyclicDependencies(const eastl::span p_uuids, + const eastl::span> p_hardReferences) +{ + ankerl::unordered_dense::map> graph; + ankerl::unordered_dense::set discovered; + + eastl::vector toQuery; + for (std::size_t i = 0; i < p_uuids.size(); ++i) + { + discovered.insert(p_uuids[i]); + graph[p_uuids[i]] = p_hardReferences[i]; + } + for (const eastl::vector& hardReferences: p_hardReferences) + { + for (const UUID& hardReference: hardReferences) + { + if (discovered.insert(hardReference).second) + { + toQuery.push_back(hardReference); + } + } + } + + while (!toQuery.empty()) + { + eastl::vector> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery); + + eastl::vector nextToQuery; + for (std::size_t i = 0; i < toQuery.size(); ++i) + { + eastl::vector& hardReferences = graph[toQuery[i]]; + hardReferences = std::move(hardReferencesBatch[i]); + + for (const UUID& hardReference: hardReferences) + { + if (discovered.insert(hardReference).second) + { + nextToQuery.push_back(hardReference); + } + } + } + + toQuery = std::move(nextToQuery); + } + + eastl::vector> waves; + eastl::vector cyclicUuids; + std::ignore = LayeredTopologicalSort(graph, discovered, waves, cyclicUuids); + return cyclicUuids; +} + +/****************************************************************************************/ + +bool SaveAsset(const UUID& p_uuid) +{ + AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(p_uuid); + CRITICAL_ASSERT(EngineAssertHandler, container != nullptr, "No AssetContainer registered for asset {}", p_uuid); + const AssetBase* asset = container->AcquireContainerHandle(p_uuid)->GetAsset(); + + const eastl::vector flatbuffer = asset->Pack(); + const eastl::vector hardReferences = asset->GetHardReferences(flatbuffer); + + const eastl::vector cyclicUuids = FindCyclicDependencies( + eastl::span {&p_uuid, 1}, + eastl::span> {&hardReferences, 1}); + BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(), + ENGINE_LOGGER, + "Cyclic HardReference dependency detected while saving asset {}, involved UUIDs: {}", + p_uuid, + cyclicUuids); + if (!cyclicUuids.empty()) + { + return false; + } + + g_dataAccessor->SaveAssetFlatbuffer(p_uuid, flatbuffer, hardReferences); + return true; +} + +/****************************************************************************************/ + +bool SaveAssets(const eastl::span p_uuids) +{ + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + + flatbuffers.reserve(p_uuids.size()); + hardReferences.reserve(p_uuids.size()); + for (const UUID& uuid: p_uuids) + { + AssetContainerBase* container = g_activeAssetLibrary->GetAssetContainer(uuid); + CRITICAL_ASSERT(EngineAssertHandler, container != nullptr, "No AssetContainer registered for asset {}", uuid); + const AssetBase* asset = container->AcquireContainerHandle(uuid)->GetAsset(); + + const eastl::vector flatbuffer = asset->Pack(); + + flatbuffers.push_back(flatbuffer); + hardReferences.push_back(asset->GetHardReferences(flatbuffer)); + } + + const eastl::vector cyclicUuids = FindCyclicDependencies(p_uuids, hardReferences); + BIGFOOT_LOG_ERROR_IF(!cyclicUuids.empty(), + ENGINE_LOGGER, + "Cyclic HardReference dependency detected while saving assets, involved UUIDs: {}", + cyclicUuids); + if (!cyclicUuids.empty()) + { + return false; + } + + g_dataAccessor->SaveAssetFlatbuffers(p_uuids, flatbuffers, hardReferences); + return true; +} +#endif +} // namespace Bigfoot::SerializationManager diff --git a/Bigfoot/Sources/Engine/touch.cpp b/Bigfoot/Sources/Engine/touch.cpp deleted file mode 100644 index cc4afd9..0000000 --- a/Bigfoot/Sources/Engine/touch.cpp +++ /dev/null @@ -1 +0,0 @@ -// to delete when an actual source is in Engine diff --git a/Bigfoot/Sources/System/CMakeLists.txt b/Bigfoot/Sources/System/CMakeLists.txt index e25e7e0..1688ada 100644 --- a/Bigfoot/Sources/System/CMakeLists.txt +++ b/Bigfoot/Sources/System/CMakeLists.txt @@ -2,8 +2,6 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}) set(PublicDependencies - $<$:quill::quill> - mimalloc stduuid::stduuid) set(PrivateDependencies) set(BigfootPublicDependencies @@ -18,11 +16,3 @@ bigfoot_create_package_lib( "") bigfoot_create_logger() - -target_compile_definitions(${PROJECT_NAME} - PUBLIC $<$:QUILL_NO_EXCEPTIONS> - PUBLIC $<$:QUILL_DISABLE_NON_PREFIXED_MACROS> - - PUBLIC MI_SHARED_LIB) -set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/MimallocImpl.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) - diff --git a/Bigfoot/Sources/System/File.cpp b/Bigfoot/Sources/System/File.cpp deleted file mode 100644 index ff57db3..0000000 --- a/Bigfoot/Sources/System/File.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/********************************************************************* - * \file File.cpp - * - * \author Romain BOULLARD - * \date December 2025 - *********************************************************************/ -#include - -namespace Bigfoot -{ -File::File(const eastl::string_view p_path): - m_path(p_path.data()), - m_pathString(p_path) -{ -} - -/****************************************************************************************/ - -bool File::IsAbsolute() const -{ - return m_path.is_absolute(); -} - -/****************************************************************************************/ - -bool File::IsRelative() const -{ - return m_path.is_relative(); -} - -/****************************************************************************************/ - -bool File::Exists() const -{ - return std::filesystem::exists(m_path); -} - -/****************************************************************************************/ - -eastl::string_view File::Path() const -{ - return m_pathString; -} - -/****************************************************************************************/ - -File File::Absolute() const -{ - return File {std::filesystem::absolute(m_path)}; -} - -/****************************************************************************************/ - -File File::Relative() const -{ - return File {std::filesystem::relative(m_path)}; -} - -/****************************************************************************************/ - -File::File(const std::filesystem::path& p_path): - m_path(p_path), - m_pathString(m_path.string().c_str()) -{ -} -} // namespace Bigfoot diff --git a/Bigfoot/Sources/System/File/File.cpp b/Bigfoot/Sources/System/File/File.cpp new file mode 100644 index 0000000..b82c669 --- /dev/null +++ b/Bigfoot/Sources/System/File/File.cpp @@ -0,0 +1,120 @@ +/********************************************************************* + * \file File.cpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#include + +#include + +#include +#include +#include + +namespace Bigfoot +{ +File::File(const eastl::string_view p_path): + m_path(p_path.data()), + m_pathString(p_path) +{ +} + +/****************************************************************************************/ + +bool File::IsAbsolute() const +{ + return m_path.is_absolute(); +} + +/****************************************************************************************/ + +bool File::IsRelative() const +{ + return m_path.is_relative(); +} + +/****************************************************************************************/ + +bool File::Exists() const +{ + return std::filesystem::exists(m_path); +} + +/****************************************************************************************/ + +eastl::string_view File::GetPath() const +{ + return m_pathString; +} + +/****************************************************************************************/ + +File File::ToAbsolute() const +{ + return File {std::filesystem::absolute(m_path)}; +} + +/****************************************************************************************/ + +File File::ToRelative() const +{ + return File {std::filesystem::relative(m_path)}; +} + +/****************************************************************************************/ + +std::size_t File::GetSize() const +{ + return static_cast(std::filesystem::file_size(m_path)); +} + +/****************************************************************************************/ + +eastl::vector File::Read(const std::size_t p_offset, const std::size_t p_size) const +{ + const File absoluteFile = ToAbsolute(); + const eastl::string_view path = absoluteFile.GetPath(); + + std::ifstream file {path.data(), std::ios::binary}; + CRITICAL_ASSERT(SystemAssertHandler, file.is_open(), "Failed to open file {} for reading", path); + + file.seekg(static_cast(p_offset), std::ios::beg); + + eastl::vector data; + data.resize(p_size); + file.read(std::bit_cast(data.data()), static_cast(data.size())); + return data; +} + +/****************************************************************************************/ + +void File::Write(const eastl::span p_data, const std::size_t p_offset) const +{ + const File absoluteFile = ToAbsolute(); + const eastl::string_view path = absoluteFile.GetPath(); + + std::ofstream {path.data(), std::ios::binary | std::ios::app}; + + std::fstream file {path.data(), std::ios::binary | std::ios::in | std::ios::out}; + CRITICAL_ASSERT(SystemAssertHandler, file.is_open(), "Failed to open file {} for writing", path); + + file.seekp(static_cast(p_offset), std::ios::beg); + file.write(std::bit_cast(p_data.data()), static_cast(p_data.size())); +} + +/****************************************************************************************/ + +void File::Delete() const +{ + std::filesystem::remove(m_path); +} + +/****************************************************************************************/ + +File::File(std::filesystem::path p_path): + m_path(std::move(p_path)), + m_pathString(m_path.string().c_str()) +{ +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/System/File/MemoryMappedFile.cpp b/Bigfoot/Sources/System/File/MemoryMappedFile.cpp new file mode 100644 index 0000000..190b968 --- /dev/null +++ b/Bigfoot/Sources/System/File/MemoryMappedFile.cpp @@ -0,0 +1,234 @@ +/********************************************************************* + * \file MemoryMappedFile.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#ifdef BIGFOOT_WINDOWS +#include +#elif defined BIGFOOT_LINUX +#include +#include +#include +#include +#endif + +namespace Bigfoot +{ +MemoryMappedFile::MemoryMappedFile(const File& p_file) +{ + const File absoluteFile = p_file.ToAbsolute(); + const eastl::string_view path = absoluteFile.GetPath(); + +#ifdef BIGFOOT_WINDOWS + m_fileHandle = CreateFileA(path.data(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, + nullptr); + CRITICAL_ASSERT(SystemAssertHandler, m_fileHandle != INVALID_HANDLE_VALUE, "Failed to open file {}", path); + + LARGE_INTEGER fileSize {}; + [[maybe_unused]] + const BOOL gotFileSize = GetFileSizeEx(m_fileHandle, &fileSize); + CRITICAL_ASSERT(SystemAssertHandler, gotFileSize, "Failed to get the size of file {}", path); + m_size = static_cast(fileSize.QuadPart); + + if (m_size != 0) + { + m_mappingHandle = CreateFileMappingA(m_fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr); + CRITICAL_ASSERT(SystemAssertHandler, m_mappingHandle != nullptr, "Failed create the file mapping {}", path); + + m_data = static_cast(MapViewOfFile(m_mappingHandle, FILE_MAP_READ, 0, 0, 0)); + CRITICAL_ASSERT(SystemAssertHandler, m_data != nullptr, "Failed to map the file {}", path); + } +#elif defined BIGFOOT_LINUX + m_fileDescriptor = open(path.data(), O_RDONLY); + CRITICAL_ASSERT(SystemAssertHandler, m_fileDescriptor != -1, "Failed to open file {}", path); + + struct stat fileStat {}; + [[maybe_unused]] + const int statResult = fstat(m_fileDescriptor, &fileStat); + CRITICAL_ASSERT(SystemAssertHandler, statResult == 0, "Failed to get the size of file {}", path); + m_size = static_cast(fileStat.st_size); + + if (m_size != 0) + { + void* const mapped = mmap(nullptr, m_size, PROT_READ, MAP_PRIVATE, m_fileDescriptor, 0); + CRITICAL_ASSERT(SystemAssertHandler, mapped != MAP_FAILED, "Failed to map file {}", path); + m_data = static_cast(mapped); + + [[maybe_unused]] + const int adviseResult = madvise(mapped, m_size, MADV_RANDOM); + } +#else + CRITICAL_ASSERT(SystemAssertHandler, false, "MemoryMappedFileBase is not implemented on this platform"); +#endif +} + +/****************************************************************************************/ + +MemoryMappedFile::~MemoryMappedFile() +{ +#ifdef BIGFOOT_WINDOWS + if (m_data != nullptr) + { + [[maybe_unused]] + const BOOL unmapped = UnmapViewOfFile(m_data); + CRITICAL_ASSERT(SystemAssertHandler, unmapped, "Failed to unmap the file"); + } + + if (m_mappingHandle != nullptr) + { + [[maybe_unused]] + const BOOL closedMapping = CloseHandle(m_mappingHandle); + CRITICAL_ASSERT(SystemAssertHandler, closedMapping, "Failed to close the file mapping"); + } + + if (m_fileHandle != INVALID_HANDLE_VALUE) + { + [[maybe_unused]] + const BOOL closedFile = CloseHandle(m_fileHandle); + CRITICAL_ASSERT(SystemAssertHandler, closedFile, "Failed to close the file"); + } +#elif defined BIGFOOT_LINUX + if (m_data != nullptr) + { + [[maybe_unused]] + const int unmapped = munmap(m_data, m_size); + CRITICAL_ASSERT(SystemAssertHandler, unmapped == 0, "Failed to unmap the file"); + } + + if (m_fileDescriptor != -1) + { + [[maybe_unused]] + const int closedFile = close(m_fileDescriptor); + CRITICAL_ASSERT(SystemAssertHandler, closedFile == 0, "Failed to close the file"); + } +#else + CRITICAL_ASSERT(SystemAssertHandler, false, "MemoryMappedFileBase is not implemented on this platform"); +#endif +} + +/****************************************************************************************/ + +eastl::span MemoryMappedFile::GetData() const +{ + return eastl::span {m_data, m_size}; +} + +/****************************************************************************************/ + +WritableMemoryMappedFile::WritableMemoryMappedFile(const File& p_file, const std::size_t p_size): + m_size(p_size) +{ + const File absoluteFile = p_file.ToAbsolute(); + const eastl::string_view path = absoluteFile.GetPath(); + +#ifdef BIGFOOT_WINDOWS + m_fileHandle = + CreateFileA(path.data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + CRITICAL_ASSERT(SystemAssertHandler, m_fileHandle != INVALID_HANDLE_VALUE, "Failed to create file {}", path); + + const LARGE_INTEGER size {.QuadPart = static_cast(p_size)}; + m_mappingHandle = CreateFileMappingA(m_fileHandle, nullptr, PAGE_READWRITE, size.HighPart, size.LowPart, nullptr); + CRITICAL_ASSERT(SystemAssertHandler, m_mappingHandle != nullptr, "Failed create the file mapping {}", path); + + m_data = static_cast(MapViewOfFile(m_mappingHandle, FILE_MAP_WRITE, 0, 0, 0)); + CRITICAL_ASSERT(SystemAssertHandler, m_data != nullptr, "Failed to map the file {}", path); +#elif defined BIGFOOT_LINUX + m_fileDescriptor = open(path.data(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + CRITICAL_ASSERT(SystemAssertHandler, m_fileDescriptor != -1, "Failed to create file {}", path); + + struct stat fileStat {}; + fstat(m_fileDescriptor, &fileStat); + if (static_cast(fileStat.st_size) < p_size) + { + [[maybe_unused]] + const int truncateResult = ftruncate(m_fileDescriptor, static_cast(p_size)); + CRITICAL_ASSERT(SystemAssertHandler, truncateResult == 0, "Failed to size file {}", path); + } + + void* const mapped = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileDescriptor, 0); + CRITICAL_ASSERT(SystemAssertHandler, mapped != MAP_FAILED, "Failed to map file {}", path); + m_data = static_cast(mapped); +#else + CRITICAL_ASSERT(SystemAssertHandler, false, "MemoryMappedFileBase is not implemented on this platform"); +#endif +} + +/****************************************************************************************/ + +WritableMemoryMappedFile::~WritableMemoryMappedFile() +{ +#ifdef BIGFOOT_WINDOWS + if (m_data != nullptr) + { + [[maybe_unused]] + const BOOL unmapped = UnmapViewOfFile(m_data); + CRITICAL_ASSERT(SystemAssertHandler, unmapped, "Failed to unmap the file"); + } + + if (m_mappingHandle != nullptr) + { + [[maybe_unused]] + const BOOL closedMapping = CloseHandle(m_mappingHandle); + CRITICAL_ASSERT(SystemAssertHandler, closedMapping, "Failed to close the file mapping"); + } + + if (m_fileHandle != INVALID_HANDLE_VALUE) + { + [[maybe_unused]] + const BOOL closedFile = CloseHandle(m_fileHandle); + CRITICAL_ASSERT(SystemAssertHandler, closedFile, "Failed to close the file"); + } +#elif defined BIGFOOT_LINUX + if (m_data != nullptr) + { + [[maybe_unused]] + const int unmapped = munmap(m_data, m_size); + CRITICAL_ASSERT(SystemAssertHandler, unmapped == 0, "Failed to unmap the file"); + } + + if (m_fileDescriptor != -1) + { + [[maybe_unused]] + const int closedFile = close(m_fileDescriptor); + CRITICAL_ASSERT(SystemAssertHandler, closedFile == 0, "Failed to close the file"); + } +#else + CRITICAL_ASSERT(SystemAssertHandler, false, "MemoryMappedFileBase is not implemented on this platform"); +#endif +} + +/****************************************************************************************/ + +eastl::span WritableMemoryMappedFile::GetData() const +{ + return eastl::span {m_data, m_size}; +} + +/****************************************************************************************/ + +void WritableMemoryMappedFile::Prefetch(const std::size_t p_offset, const std::size_t p_size) const +{ +#ifdef BIGFOOT_WINDOWS + WIN32_MEMORY_RANGE_ENTRY range {.VirtualAddress = m_data + p_offset, .NumberOfBytes = p_size}; + [[maybe_unused]] + const BOOL prefetched = PrefetchVirtualMemory(GetCurrentProcess(), 1, &range, 0); +#elif defined BIGFOOT_LINUX + [[maybe_unused]] + const int adviseResult = madvise(m_data + p_offset, p_size, MADV_WILLNEED); +#else + CRITICAL_ASSERT(SystemAssertHandler, false, "MemoryMappedFileBase is not implemented on this platform"); +#endif +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/System/Include/System/File.hpp b/Bigfoot/Sources/System/Include/System/File/File.hpp similarity index 61% rename from Bigfoot/Sources/System/Include/System/File.hpp rename to Bigfoot/Sources/System/Include/System/File/File.hpp index 0c67f62..aae0d40 100644 --- a/Bigfoot/Sources/System/Include/System/File.hpp +++ b/Bigfoot/Sources/System/Include/System/File/File.hpp @@ -7,8 +7,10 @@ #ifndef BIGFOOT_SYSTEM_FILE_HPP #define BIGFOOT_SYSTEM_FILE_HPP +#include #include #include +#include #include @@ -24,9 +26,6 @@ class File */ File(const eastl::string_view p_path); - File(const File& p_path) = default; - File(File&& p_path) = default; - /** * Check if the file is relative to the current executable * @@ -57,7 +56,7 @@ class File * \return The path */ [[nodiscard]] - eastl::string_view Path() const; + eastl::string_view GetPath() const; /** * Get the same file, relative to the current executable @@ -65,7 +64,7 @@ class File * \return The relative file */ [[nodiscard]] - File Relative() const; + File ToRelative() const; /** * Get the same file, with an absolute path @@ -73,12 +72,39 @@ class File * \return The absolute file */ [[nodiscard]] - File Absolute() const; + File ToAbsolute() const; - ~File() = default; + /** + * Get the size of the file + * + * \return The size in bytes + */ + [[nodiscard]] + std::size_t GetSize() const; - File& operator=(const File& p_path) = default; - File& operator=(File&& p_path) = default; + /** + * Read bytes from the file + * + * \param p_offset The offset in bytes + * \param p_size The number of bytes to read + * \return The bytes read + */ + [[nodiscard]] + eastl::vector Read(std::size_t p_offset, std::size_t p_size) const; + + /** + * Write bytes to the file at a given offset, creating the file if needed. Content before + * p_offset, or from past p_offset + p_data.size(), is left untouched. + * + * \param p_data The bytes to write + * \param p_offset The offset, in bytes, to start writing at + */ + void Write(eastl::span p_data, std::size_t p_offset) const; + + /** + * Delete the file + */ + void Delete() const; private: /** @@ -86,7 +112,7 @@ class File * * \param p_path The path */ - File(const std::filesystem::path& p_path); + File(std::filesystem::path p_path); /** * The path diff --git a/Bigfoot/Sources/System/Include/System/File/MemoryMappedFile.hpp b/Bigfoot/Sources/System/Include/System/File/MemoryMappedFile.hpp new file mode 100644 index 0000000..326f6d2 --- /dev/null +++ b/Bigfoot/Sources/System/Include/System/File/MemoryMappedFile.hpp @@ -0,0 +1,150 @@ +/********************************************************************* + * \file MemoryMappedFile.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_SYSTEM_MEMORYMAPPEDFILE_HPP +#define BIGFOOT_SYSTEM_MEMORYMAPPEDFILE_HPP +#include + +#include + +#include +#include + +namespace Bigfoot +{ +class MemoryMappedFile +{ + public: + /** + * Constructor + * Opens and maps the whole file + * + * \param p_file The file to map + */ + explicit MemoryMappedFile(const File& p_file); + + MemoryMappedFile(const MemoryMappedFile& p_file) = delete; + MemoryMappedFile(MemoryMappedFile&& p_file) = delete; + + /** + * Destructor + * + */ + ~MemoryMappedFile(); + + /** + * Get the mapped content of the file + * + * \return The mapped bytes + */ + [[nodiscard]] + eastl::span GetData() const; + + MemoryMappedFile& operator=(const MemoryMappedFile& p_file) = delete; + MemoryMappedFile& operator=(MemoryMappedFile&& p_file) = delete; + + private: +#ifdef BIGFOOT_WINDOWS + /** + * Handle to the file + */ + void* m_fileHandle = nullptr; + + /** + * Handle to the file mapping + */ + void* m_mappingHandle = nullptr; +#elif defined BIGFOOT_LINUX + /** + * Handle to the file + */ + int m_fileDescriptor = -1; +#endif + + /** + * The mapped size + */ + std::size_t m_size = 0; + + /** + * The mapped data + */ + std::byte* m_data = nullptr; +}; + +/****************************************************************************************/ + +class WritableMemoryMappedFile +{ + public: + /** + * Constructor + * Creates and maps the file + * + * \param p_file The file to create and map + * \param p_size The size to map + */ + WritableMemoryMappedFile(const File& p_file, std::size_t p_size); + + WritableMemoryMappedFile(const WritableMemoryMappedFile& p_file) = delete; + WritableMemoryMappedFile(WritableMemoryMappedFile&& p_file) = delete; + + /** + * Destructor + * + */ + ~WritableMemoryMappedFile(); + + /** + * Get the mapped content of the file + * + * \return The mapped bytes + */ + [[nodiscard]] + eastl::span GetData() const; + + /** + * Hint the OS to prefetch a range of data, so writes into that range do not each pay a page fault + * + * \param p_offset The offset, in bytes, to start prefetching from + * \param p_size The number of bytes to prefetch + */ + void Prefetch(std::size_t p_offset, std::size_t p_size) const; + + WritableMemoryMappedFile& operator=(const WritableMemoryMappedFile& p_file) = delete; + WritableMemoryMappedFile& operator=(WritableMemoryMappedFile&& p_file) = delete; + + private: +#ifdef BIGFOOT_WINDOWS + /** + * Handle to the file + */ + void* m_fileHandle = nullptr; + + /** + * Handle to the file mapping + */ + void* m_mappingHandle = nullptr; +#elif defined BIGFOOT_LINUX + /** + * Handle to the file + */ + int m_fileDescriptor = -1; +#endif + + /** + * The mapped size + */ + std::size_t m_size = 0; + + /** + * The mapped data + */ + std::byte* m_data = nullptr; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/System/Include/System/Log/EASTLFormatters.hpp b/Bigfoot/Sources/System/Include/System/Log/EASTLFormatters.hpp deleted file mode 100644 index 8d2411c..0000000 --- a/Bigfoot/Sources/System/Include/System/Log/EASTLFormatters.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/********************************************************************* - * \file EASTLFormatters.hpp - * - * \author Romain BOULLARD - * \date December 2025 - *********************************************************************/ -#ifndef BIGFOOT_SYSTEM_EASTLFORMATTERS_HPP -#define BIGFOOT_SYSTEM_EASTLFORMATTERS_HPP -#include - -#if defined(BIGFOOT_NOT_OPTIMIZED) -#include -#endif - -#include - -#include -#include - -// STRING - -template<> -struct std::formatter -{ - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - template - auto format(const eastl::string& p_string, FormatContext& ctx) const - { - return std::format_to(ctx.out(), "{}", p_string.c_str()); - } -}; - -#if defined BIGFOOT_NOT_OPTIMIZED -template<> -struct fmtquill::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const eastl::string& p_string, format_context& ctx) const - { - return fmtquill::format_to(ctx.out(), "{}", p_string.c_str()); - } -}; - -template<> -struct quill::Codec: quill::DeferredFormatCodec -{ -}; -#endif - -// STRING_VIEW - -template<> -struct std::formatter -{ - constexpr auto parse(std::format_parse_context& ctx) - { - return ctx.begin(); - } - - template - auto format(const eastl::string_view& p_stringView, FormatContext& ctx) const - { - return std::format_to(ctx.out(), "{}", p_stringView.data()); - } -}; - -#if defined BIGFOOT_NOT_OPTIMIZED -template<> -struct fmtquill::formatter -{ - constexpr auto parse(format_parse_context& ctx) - { - return ctx.begin(); - } - - auto format(const eastl::string_view& p_stringView, format_context& ctx) const - { - return fmtquill::format_to(ctx.out(), "{}", p_stringView.data()); - } -}; - -template<> -struct quill::Codec: quill::DeferredFormatCodec -{ -}; -#endif -#endif diff --git a/Bigfoot/Sources/System/Include/System/Log/Log.hpp b/Bigfoot/Sources/System/Include/System/Log/Log.hpp deleted file mode 100644 index 37c7800..0000000 --- a/Bigfoot/Sources/System/Include/System/Log/Log.hpp +++ /dev/null @@ -1,182 +0,0 @@ -/********************************************************************* - * \file Log.hpp - * - * \author Romain BOULLARD - * \date October 2025 - *********************************************************************/ -#ifndef BIGFOOT_SYSTEM_LOG_HPP -#define BIGFOOT_SYSTEM_LOG_HPP -#include - -#if defined BIGFOOT_NOT_OPTIMIZED -#include - -#include - -#include - -#ifdef BIGFOOT_WINDOWS -#pragma warning(disable: 4702) -#endif -#include -#include -#include -#include -#include -#if defined BIGFOOT_WINDOWS -#pragma warning(default: 4702) -#endif - -namespace Bigfoot -{ -class Log -{ - public: - struct LoggerInfo - { - std::string m_name; - Flat::LogLevel m_level; - }; - - Log(); - - Log(const Log& p_logger) = delete; - Log(Log&& p_logger) = delete; - - /** - * Register a logger. - * - * \param p_loggerInfo The logger to register - */ - [[nodiscard]] - quill::Logger* RegisterLogger(const LoggerInfo& p_loggerInfo); - - /** - * Register a logger. - * - * \param p_loggerInfo The logger to get - * \return The logger, nullptr if it does not exist - */ - [[nodiscard]] - quill::Logger* GetLogger(const LoggerInfo& p_loggerInfo); - - /** - * Changes the loglevel of a Logger. - * - * \param p_loggerInfo The logger to change - * \param p_level The new level - */ - void ChangeLoggerLogLevel(LoggerInfo& p_loggerInfo, const Flat::LogLevel p_level); - - ~Log(); - - Log& operator=(const Log& p_logger) = delete; - Log& operator=(Log&& p_logger) = delete; - - private: - /** - * Set the LogLevel of a logger - * - * \param p_loggerInfo The logger to set - */ - void SetLoggerLevel(const LoggerInfo& p_loggerInfo); - - /* - * The sinks - */ - eastl::array, 1> m_sinks; -}; -} // namespace Bigfoot - -#define BIGFOOT_LOG_DEBUG(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_DEBUG(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_DEBUG(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) - -#define BIGFOOT_LOG_TRACE(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_TRACE_L3(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_TRACE_L3(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) - -#define BIGFOOT_LOG_INFO(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_INFO(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_INFO(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) - -#define BIGFOOT_LOG_WARN(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_WARNING(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_WARNING(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) - -#define BIGFOOT_LOG_ERROR(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_ERROR(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_ERROR(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) - -#define BIGFOOT_LOG_FATAL(loggerName, fmt, ...) \ - do \ - { \ - if (quill::Logger* logger = Bigfoot::Singleton::Instance().GetLogger(loggerName)) \ - { \ - QUILL_LOG_CRITICAL(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - else \ - { \ - QUILL_LOG_CRITICAL(Bigfoot::Singleton::Instance().RegisterLogger(loggerName), \ - fmt __VA_OPT__(, ) __VA_ARGS__); \ - } \ - } while (0) -#else -#define BIGFOOT_LOG_DEBUG(loggerName, fmt, ...) -#define BIGFOOT_LOG_TRACE(loggerName, fmt, ...) -#define BIGFOOT_LOG_INFO(loggerName, fmt, ...) -#define BIGFOOT_LOG_WARN(loggerName, fmt, ...) -#define BIGFOOT_LOG_ERROR(loggerName, fmt, ...) -#define BIGFOOT_LOG_FATAL(loggerName, fmt, ...) -#endif -#endif diff --git a/Bigfoot/Sources/System/Include/System/SystemAssertHandler.hpp b/Bigfoot/Sources/System/Include/System/SystemAssertHandler.hpp index 6e58208..d10732d 100644 --- a/Bigfoot/Sources/System/Include/System/SystemAssertHandler.hpp +++ b/Bigfoot/Sources/System/Include/System/SystemAssertHandler.hpp @@ -6,55 +6,15 @@ *********************************************************************/ #ifndef BIGFOOT_SYSTEM_SYSTEMASSERTHANDLER_HPP #define BIGFOOT_SYSTEM_SYSTEMASSERTHANDLER_HPP -#include - #include -#if defined BIGFOOT_NOT_OPTIMIZED -#include +#include -#include -#include -#include +#ifdef BIGFOOT_NOT_OPTIMIZED namespace Bigfoot { -class SystemAssertHandler -{ - public: - SystemAssertHandler() = delete; - - SystemAssertHandler(const SystemAssertHandler& p_handler) = delete; - SystemAssertHandler(SystemAssertHandler&& p_handler) = delete; - - ~SystemAssertHandler() = delete; - - /** - * Handle an assertion. - * - * \param p_location Location of the assertion. - * \param p_stacktrace The stack trace - * \param p_format Format string for the assertion message. - * \param p_args Arguments for the format string. - */ - template - static void Handle(const std::source_location& p_location, - const std::string_view p_stacktrace, - std::format_string p_format, - ARGS&&... p_args) - { - BIGFOOT_LOG_FATAL(SYSTEM_LOGGER, - "Assert: {} (File:{}, Line:{}, Function:{}\n{}", - std::format(p_format, eastl::forward(p_args)...), - p_location.file_name(), - p_location.line(), - p_location.function_name(), - p_stacktrace); - } - - SystemAssertHandler& operator=(const SystemAssertHandler& p_handler) = delete; - SystemAssertHandler& operator=(SystemAssertHandler&& p_handler) = delete; -}; +using SystemAssertHandler = AssertHandler; } // namespace Bigfoot #endif #endif diff --git a/Bigfoot/Sources/System/Include/System/SystemLogger_generated.hpp b/Bigfoot/Sources/System/Include/System/SystemLogger_generated.hpp index dcdf934..cbafc52 100644 --- a/Bigfoot/Sources/System/Include/System/SystemLogger_generated.hpp +++ b/Bigfoot/Sources/System/Include/System/SystemLogger_generated.hpp @@ -6,7 +6,7 @@ *********************************************************************/ #ifndef BIGFOOT_SYSTEMLOGGER_GENERATED_HPP #define BIGFOOT_SYSTEMLOGGER_GENERATED_HPP -#include +#include #if defined BIGFOOT_NOT_OPTIMIZED diff --git a/Bigfoot/Sources/System/Include/System/Time/Time.fbs b/Bigfoot/Sources/System/Include/System/Time/Time.fbs new file mode 100644 index 0000000..fc9ae88 --- /dev/null +++ b/Bigfoot/Sources/System/Include/System/Time/Time.fbs @@ -0,0 +1,8 @@ +native_include "System/Time/Time.hpp"; + +namespace Flat.Bigfoot; + +struct Time (native_type: "::Bigfoot::Time") +{ + epoch:ulong; +} \ No newline at end of file diff --git a/Bigfoot/Sources/System/Include/System/Time/Time.hpp b/Bigfoot/Sources/System/Include/System/Time/Time.hpp new file mode 100644 index 0000000..03709ad --- /dev/null +++ b/Bigfoot/Sources/System/Include/System/Time/Time.hpp @@ -0,0 +1,81 @@ +/********************************************************************* + * \file Time.hpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#ifndef BIGFOOT_SYSTEM_TIME_TIME_HPP +#define BIGFOOT_SYSTEM_TIME_TIME_HPP +#include +#include +#include + +namespace Bigfoot +{ +class Time +{ + public: + Time(const std::uint64_t p_epoch); + + [[nodiscard]] + std::uint32_t GetYear() const; + + [[nodiscard]] + std::uint32_t GetMonth() const; + + [[nodiscard]] + std::uint32_t GetDay() const; + + [[nodiscard]] + std::uint32_t GetHour() const; + + [[nodiscard]] + std::uint32_t GetMinute() const; + + [[nodiscard]] + std::uint32_t GetSecond() const; + + [[nodiscard]] + std::uint32_t GetMicrosecond() const; + + [[nodiscard]] + std::uint64_t GetEpoch() const; + + [[nodiscard]] + bool operator==(const Time& p_other) const + { + return m_epoch == p_other.m_epoch; + } + + [[nodiscard]] + auto operator<=>(const Time& p_other) const + { + return m_epoch <=> p_other.m_epoch; + } + + [[nodiscard]] + static Time Now(); + + private: + std::uint64_t m_epoch; + std::tm m_timeInfo; + std::chrono::microseconds m_microseconds; +}; +} // namespace Bigfoot + +/****************************************************************************************/ + +namespace Flat::Bigfoot +{ +struct Time; +} // namespace Flat::Bigfoot + +/****************************************************************************************/ + +namespace flatbuffers +{ +Flat::Bigfoot::Time Pack(const Bigfoot::Time& p_time); +Bigfoot::Time UnPack(const Flat::Bigfoot::Time& p_flatTime); +} // namespace flatbuffers + +#endif diff --git a/Bigfoot/Sources/System/Include/System/Time/Time_generated.hpp b/Bigfoot/Sources/System/Include/System/Time/Time_generated.hpp new file mode 100644 index 0000000..b71046f --- /dev/null +++ b/Bigfoot/Sources/System/Include/System/Time/Time_generated.hpp @@ -0,0 +1,78 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_TIME_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_TIME_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/Time/Time.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct Time; + +inline const ::flatbuffers::TypeTable *TimeTypeTable(); + +FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) Time FLATBUFFERS_FINAL_CLASS { + private: + uint64_t epoch_; + + public: + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return TimeTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.Time"; + } + Time() + : epoch_(0) { + } + Time(uint64_t _epoch) + : epoch_(::flatbuffers::EndianScalar(_epoch)) { + } + uint64_t epoch() const { + return ::flatbuffers::EndianScalar(epoch_); + } +}; +FLATBUFFERS_STRUCT_END(Time, 8); + +struct Time::Traits { + using type = Time; +}; + +inline const ::flatbuffers::TypeTable *TimeTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_ULONG, 0, -1 } + }; + static const int64_t values[] = { 0, 8 }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_STRUCT, 1, type_codes, nullptr, nullptr, values, nullptr + }; + return &tt; +} + +} // namespace Bigfoot +} // namespace Flat + +#endif // FLATBUFFERS_GENERATED_TIME_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Sources/System/Include/System/UUID/FlatUUID.hpp b/Bigfoot/Sources/System/Include/System/UUID/FlatUUID.hpp index d8e7f7d..7635d32 100644 --- a/Bigfoot/Sources/System/Include/System/UUID/FlatUUID.hpp +++ b/Bigfoot/Sources/System/Include/System/UUID/FlatUUID.hpp @@ -11,8 +11,8 @@ namespace flatbuffers { -Bigfoot::Flat::UUID Pack(const Bigfoot::UUID& p_uuid); -Bigfoot::UUID UnPack(const Bigfoot::Flat::UUID& p_flatUUID); +Flat::Bigfoot::UUID Pack(const Bigfoot::UUID& p_uuid); +Bigfoot::UUID UnPack(const Flat::Bigfoot::UUID& p_flatUUID); } // namespace flatbuffers #endif diff --git a/Bigfoot/Sources/System/Include/System/UUID/UUID.fbs b/Bigfoot/Sources/System/Include/System/UUID/UUID.fbs index c622b85..6fab320 100644 --- a/Bigfoot/Sources/System/Include/System/UUID/UUID.fbs +++ b/Bigfoot/Sources/System/Include/System/UUID/UUID.fbs @@ -1,6 +1,6 @@ -native_include "System/UUID/FlatUUID.hpp"; +native_include "System/UUID/UUID.hpp"; -namespace Bigfoot.Flat; +namespace Flat.Bigfoot; struct UUID (native_type: "::Bigfoot::UUID") { diff --git a/Bigfoot/Sources/System/Include/System/UUID/UUID.hpp b/Bigfoot/Sources/System/Include/System/UUID/UUID.hpp index 21ff3b1..42c57f1 100644 --- a/Bigfoot/Sources/System/Include/System/UUID/UUID.hpp +++ b/Bigfoot/Sources/System/Include/System/UUID/UUID.hpp @@ -4,9 +4,11 @@ * \author Romain BOULLARD * \date October 2025 *********************************************************************/ -#ifndef BIGFOOT_SYSTEM_UUID_HPP -#define BIGFOOT_SYSTEM_UUID_HPP - +#ifndef BIGFOOT_SYSTEM_UUID_UUID_HPP +#define BIGFOOT_SYSTEM_UUID_UUID_HPP +#include +#include +#include #include namespace Bigfoot @@ -16,6 +18,11 @@ class UUID public: static constexpr std::uint32_t UUID_BYTE_SIZE = 16; + /** + * Constructor + * Will default initialize an UUID to a valid value + * + */ UUID(); /** @@ -32,22 +39,26 @@ class UUID */ explicit UUID(const std::span p_raw); - UUID(const UUID& p_uuid) = default; - UUID(UUID&& p_uuid) noexcept = default; + /** + * Gets the raw bytes of a UUID + * + * \return A view to the raw bytes + */ + [[nodiscard]] + std::span ToBytes() const; - ~UUID() = default; + /** + * Gets the string representation of a UUID + * + * \return A string representing the UUID + */ + [[nodiscard]] + eastl::string ToString() const; - operator std::span() const; - operator std::string() const; operator bool() const; - UUID& operator=(const UUID& p_uuid) = default; - UUID& operator=(UUID&& p_uuid) noexcept = default; - [[nodiscard]] - bool operator==(const UUID& p_uuid) const = default; - [[nodiscard]] - bool operator!=(const UUID& p_uuid) const = default; + bool operator==(const UUID& p_uuid) const; [[nodiscard]] bool operator<(const UUID& p_uuid) const; @@ -73,8 +84,6 @@ class UUID static UUID NULL_UUID; private: - friend struct std::hash; - /** * The uuid. */ @@ -97,27 +106,34 @@ class UUID template<> struct std::hash { + using is_avalanching = void; + + [[nodiscard]] std::size_t operator()(const Bigfoot::UUID& p_uuid) const noexcept { - return std::hash()(p_uuid.m_uuid); + const auto bytes = p_uuid.ToBytes(); + return ankerl::unordered_dense::detail::wyhash::hash(bytes.data(), bytes.size()); } }; +/****************************************************************************************/ + template<> -struct std::formatter +struct fmt::formatter { - constexpr auto parse(std::format_parse_context& ctx) + static constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } - template - auto format(const Bigfoot::UUID& p_uuid, FormatContext& ctx) const + static auto format(const Bigfoot::UUID& p_uuid, format_context& ctx) { - return std::format_to(ctx.out(), "{}", static_cast(p_uuid)); + return fmt::format_to(ctx.out(), "{}", p_uuid.ToString().c_str()); } }; +/****************************************************************************************/ + #if defined BIGFOOT_NOT_OPTIMIZED #include @@ -131,7 +147,7 @@ struct fmtquill::formatter auto format(const Bigfoot::UUID& p_uuid, format_context& ctx) const { - return fmtquill::format_to(ctx.out(), "{}", static_cast(p_uuid)); + return fmtquill::format_to(ctx.out(), "{}", p_uuid.ToString().c_str()); } }; @@ -141,4 +157,19 @@ struct quill::Codec: quill::DeferredFormatCodec }; #endif +/****************************************************************************************/ + +namespace Flat::Bigfoot +{ +struct UUID; +} // namespace Flat::Bigfoot + +/****************************************************************************************/ + +namespace flatbuffers +{ +Flat::Bigfoot::UUID Pack(const Bigfoot::UUID& p_uuid); +Bigfoot::UUID UnPack(const Flat::Bigfoot::UUID& p_flatUUID); +} // namespace flatbuffers + #endif diff --git a/Bigfoot/Sources/System/Include/System/UUID/UUID_generated.hpp b/Bigfoot/Sources/System/Include/System/UUID/UUID_generated.hpp index f02e7fa..f6b35a0 100644 --- a/Bigfoot/Sources/System/Include/System/UUID/UUID_generated.hpp +++ b/Bigfoot/Sources/System/Include/System/UUID/UUID_generated.hpp @@ -1,8 +1,8 @@ // automatically generated by the FlatBuffers compiler, do not modify -#ifndef FLATBUFFERS_GENERATED_UUID_BIGFOOT_FLAT_H_ -#define FLATBUFFERS_GENERATED_UUID_BIGFOOT_FLAT_H_ +#ifndef FLATBUFFERS_GENERATED_UUID_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_UUID_FLAT_BIGFOOT_H_ #include "flatbuffers/flatbuffers.h" @@ -12,14 +12,22 @@ static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && FLATBUFFERS_VERSION_MINOR == 12 && FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); -#include "System/UUID/FlatUUID.hpp" +#include "System/UUID/UUID.hpp" #include "EASTL/unique_ptr.h" #include "EASTL/string.h" +#include "EASTL/vector.h" -namespace Bigfoot { namespace Flat { +namespace Bigfoot { struct UUID; @@ -35,7 +43,7 @@ FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) UUID FLATBUFFERS_FINAL_CLASS { return UUIDTypeTable(); } static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { - return "Bigfoot.Flat.UUID"; + return "Flat.Bigfoot.UUID"; } UUID() : bytes_() { @@ -59,16 +67,13 @@ inline const ::flatbuffers::TypeTable *UUIDTypeTable() { }; static const int16_t array_sizes[] = { 16, }; static const int64_t values[] = { 0, 16 }; - static const char * const names[] = { - "bytes" - }; static const ::flatbuffers::TypeTable tt = { - ::flatbuffers::ST_STRUCT, 1, type_codes, nullptr, array_sizes, values, names + ::flatbuffers::ST_STRUCT, 1, type_codes, nullptr, array_sizes, values, nullptr }; return &tt; } -} // namespace Flat } // namespace Bigfoot +} // namespace Flat -#endif // FLATBUFFERS_GENERATED_UUID_BIGFOOT_FLAT_H_ +#endif // FLATBUFFERS_GENERATED_UUID_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Sources/System/Time.cpp b/Bigfoot/Sources/System/Time/Time.cpp similarity index 78% rename from Bigfoot/Sources/System/Time.cpp rename to Bigfoot/Sources/System/Time/Time.cpp index 672f027..e6e3a21 100644 --- a/Bigfoot/Sources/System/Time.cpp +++ b/Bigfoot/Sources/System/Time/Time.cpp @@ -4,9 +4,10 @@ * \author Romain BOULLARD * \date December 2025 *********************************************************************/ -#include +#include #include +#include namespace { @@ -54,56 +55,56 @@ Time::Time(const std::uint64_t p_epoch): /****************************************************************************************/ -std::uint32_t Time::Year() const +std::uint32_t Time::GetYear() const { return m_timeInfo.tm_year + 1900; } /****************************************************************************************/ -std::uint32_t Time::Month() const +std::uint32_t Time::GetMonth() const { return m_timeInfo.tm_mon + 1; } /****************************************************************************************/ -std::uint32_t Time::Day() const +std::uint32_t Time::GetDay() const { return m_timeInfo.tm_mday; } /****************************************************************************************/ -std::uint32_t Time::Hour() const +std::uint32_t Time::GetHour() const { return m_timeInfo.tm_hour; } /****************************************************************************************/ -std::uint32_t Time::Minute() const +std::uint32_t Time::GetMinute() const { return m_timeInfo.tm_min; } /****************************************************************************************/ -std::uint32_t Time::Second() const +std::uint32_t Time::GetSecond() const { return m_timeInfo.tm_sec; } /****************************************************************************************/ -std::uint32_t Time::Microsecond() const +std::uint32_t Time::GetMicrosecond() const { return static_cast(m_microseconds.count()); } /****************************************************************************************/ -std::uint64_t Time::Epoch() const +std::uint64_t Time::GetEpoch() const { return m_epoch; } @@ -119,3 +120,20 @@ Time Time::Now() return Time {epochInMicroSeconds}; } } // namespace Bigfoot + +/****************************************************************************************/ + +namespace flatbuffers +{ +Flat::Bigfoot::Time Pack(const Bigfoot::Time& p_time) +{ + return Flat::Bigfoot::Time {p_time.GetEpoch()}; +} + +/****************************************************************************************/ + +Bigfoot::Time UnPack(const Flat::Bigfoot::Time& p_flatTime) +{ + return Bigfoot::Time {p_flatTime.epoch()}; +} +} // namespace flatbuffers diff --git a/Bigfoot/Sources/System/UUID/FlatUUID.cpp b/Bigfoot/Sources/System/UUID/FlatUUID.cpp deleted file mode 100644 index 78bfeb9..0000000 --- a/Bigfoot/Sources/System/UUID/FlatUUID.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/********************************************************************* - * \file FlatUUID.cpp - * - * \author Romain BOULLARD - * \date December 2025 - *********************************************************************/ -#include - -namespace flatbuffers -{ -Bigfoot::Flat::UUID Pack(const Bigfoot::UUID& p_uuid) -{ - const std::span bytes = p_uuid; - return Bigfoot::Flat::UUID { - std::span {reinterpret_cast(bytes.data()), - bytes.size()}}; -} - -/****************************************************************************************/ - -Bigfoot::UUID UnPack(const Bigfoot::Flat::UUID& p_flatUUID) -{ - const std::span bytesView {p_flatUUID.bytes()->data(), - p_flatUUID.bytes()->size()}; - return Bigfoot::UUID {std::as_bytes(bytesView)}; -} -} // namespace flatbuffers diff --git a/Bigfoot/Sources/System/UUID/UUID.cpp b/Bigfoot/Sources/System/UUID/UUID.cpp index cd87cb6..9c7ada1 100644 --- a/Bigfoot/Sources/System/UUID/UUID.cpp +++ b/Bigfoot/Sources/System/UUID/UUID.cpp @@ -6,6 +6,8 @@ *********************************************************************/ #include +#include + namespace Bigfoot { UUID UUID::NULL_UUID {"00000000-0000-0000-0000-000000000000"}; @@ -41,16 +43,17 @@ UUID::UUID(const std::span p_raw) /****************************************************************************************/ -UUID::operator std::span() const +std::span UUID::ToBytes() const { return m_uuid.as_bytes(); } /****************************************************************************************/ -UUID::operator std::string() const +eastl::string UUID::ToString() const { - return uuids::to_string(m_uuid); + const std::string uuid = uuids::to_string(m_uuid); + return eastl::string {uuid.data(), uuid.size()}; } /****************************************************************************************/ @@ -60,6 +63,11 @@ UUID::operator bool() const return *this != NULL_UUID; } +bool UUID::operator==(const UUID& p_uuid) const +{ + return m_uuid == p_uuid.m_uuid; +} + /****************************************************************************************/ bool UUID::operator<(const UUID& p_uuid) const @@ -85,3 +93,25 @@ uuids::uuid_random_generator UUID::GetUUIDGenerator() return uuids::uuid_random_generator {ms_randomGenerator}; } } // namespace Bigfoot + +/****************************************************************************************/ + +namespace flatbuffers +{ +Flat::Bigfoot::UUID Pack(const Bigfoot::UUID& p_uuid) +{ + const std::span bytes = p_uuid.ToBytes(); + return Flat::Bigfoot::UUID { + std::span {reinterpret_cast(bytes.data()), + bytes.size()}}; +} + +/****************************************************************************************/ + +Bigfoot::UUID UnPack(const Flat::Bigfoot::UUID& p_flatUUID) +{ + const std::span bytesView {p_flatUUID.bytes()->data(), + p_flatUUID.bytes()->size()}; + return Bigfoot::UUID {std::as_bytes(bytesView)}; +} +} // namespace flatbuffers 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..d4539c0 --- /dev/null +++ b/Bigfoot/Sources/Tools/Executables/SasquatchBaker/main.cpp @@ -0,0 +1,50 @@ +/********************************************************************* + * \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; + + const 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 << '\n'; + return 1; + } + + if (showHelp) + { + std::cout << cli << '\n'; + 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..b3b4aad --- /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 + +#ifdef 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..97f6ea2 --- /dev/null +++ b/Bigfoot/Sources/Tools/SasquatchBaker/SasquatchBaker.cpp @@ -0,0 +1,65 @@ +/********************************************************************* + * \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); + } + + std::uint64_t totalHardReferencesBytes = 0; + { + BigFile::Request sizeRequest {p_bigFile, + "SELECT COALESCE(SUM(LENGTH(HardReference)), 0) FROM AssetHardReference;"}; + [[maybe_unused]] + const bool hasSize = sizeRequest.Step(); + ASSERT(SasquatchBakerAssertHandler, hasSize, "Failed to sum the hardreferences sizes of the BigFile"); + totalHardReferencesBytes = sizeRequest.Get(0); + } + + SasquatchWriter writer {p_outputFile, assetCount, totalFlatbufferBytes, totalHardReferencesBytes}; + + BigFile::Request request {p_bigFile, "SELECT UUID FROM Asset;"}; + while (request.Step()) + { + const std::span rawUUID = request.Get(0); + + ASSERT(SasquatchBakerAssertHandler, + rawUUID.size() == UUID::UUID_BYTE_SIZE, + "Unexpected UUID blob size in BigFile (got {} / expected {})", + rawUUID.size(), + UUID::UUID_BYTE_SIZE); + + const UUID uuid {std::span {rawUUID.data(), UUID::UUID_BYTE_SIZE}}; + const eastl::vector flatbuffer = p_bigFile.GetAssetFlatbuffer(uuid); + const eastl::vector hardReferences = p_bigFile.GetAssetHardReferences(uuid); + + writer.WriteAsset(uuid, flatbuffer, hardReferences); + } +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Utils/CMakeLists.txt b/Bigfoot/Sources/Utils/CMakeLists.txt index f12f19a..26ed4ab 100644 --- a/Bigfoot/Sources/Utils/CMakeLists.txt +++ b/Bigfoot/Sources/Utils/CMakeLists.txt @@ -2,7 +2,15 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}) set(PublicDependencies - $<$:cpptrace::cpptrace>) + $<$:quill::quill> + $,mimalloc-asan,mimalloc-static> + unordered_dense::unordered_dense + EASTL::EASTL + EAStdC::EAStdC + fmt::fmt + flatbuffers::flatbuffers + rapidhash::rapidhash + effolkronium_random) set(PrivateDependencies) set(BigfootPublicDependencies) set(BigfootPrivateDependencies) @@ -12,4 +20,13 @@ bigfoot_create_package_lib( "${PrivateLibraries}" "${BigfootPublicDependencies}" "${BigfootPrivateDependencies}" - "") \ No newline at end of file + "") +set_source_files_properties(../Utils/MimallocImpl.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) +set_source_files_properties(../Utils/EASTLVsnprintfImpl.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) + +bigfoot_create_logger() + +target_compile_definitions(${PROJECT_NAME} + PUBLIC + $<$:QUILL_NO_EXCEPTIONS> + $<$:QUILL_DISABLE_NON_PREFIXED_MACROS>) \ No newline at end of file diff --git a/Bigfoot/Sources/Utils/Include/Utils/Algorithm/Graph.hpp b/Bigfoot/Sources/Utils/Include/Utils/Algorithm/Graph.hpp new file mode 100644 index 0000000..8734c60 --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Algorithm/Graph.hpp @@ -0,0 +1,154 @@ +/********************************************************************* + * \file Graph.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_ALGORITHM_GRAPH_HPP +#define BIGFOOT_UTILS_ALGORITHM_GRAPH_HPP +#include + +#include +#include + +#include +#include + +namespace Bigfoot +{ +/* + * Kahn's algorithm + * + * \param p_graph Every discovered node's outgoing edges (eg: its dependencies) + * \param p_nodes The set of nodes to sort + * \param p_waves The sorted nodes + * \param p_cyclicNodes Nodes that could not be sorted (ie: part of a cycle) + * \return True if a cycle was found + */ +template +[[nodiscard]] +bool LayeredTopologicalSort(const ankerl::unordered_dense::map>& p_graph, + const ankerl::unordered_dense::set& p_nodes, + eastl::vector>& p_waves, + eastl::vector& p_cyclicNodes) +{ + ASSERT(UtilsAssertHandler, p_waves.empty(), "p_waves must be empty"); + ASSERT(UtilsAssertHandler, p_cyclicNodes.empty(), "p_cyclicNodes must be empty"); + ASSERT(UtilsAssertHandler, + p_nodes.size() <= std::numeric_limits::max(), + "Can't sort more than {} nodes", + std::numeric_limits::max()); + + const auto nodeCount = static_cast(p_nodes.size()); + + ankerl::unordered_dense::map nodeToIndex; + nodeToIndex.reserve(nodeCount); + eastl::vector indexToNode; + indexToNode.reserve(nodeCount); + for (const NODE_TYPE& node: p_nodes) + { + nodeToIndex.emplace(node, static_cast(indexToNode.size())); + indexToNode.push_back(node); + } + + // Step 1: Compute in-degree for each node + eastl::vector inDegree(nodeCount, 0); + + // Reverse adjacency (which nodes depend on each node) stored as CSR: adjacencyOffsets[i]..adjacencyOffsets[i + 1] + // is node i's slice of adjacency. Built in two passes (count listeners per node, then fill) so the whole + // structure is a couple of flat allocations instead of one per node. + eastl::vector adjacencyOffsets(nodeCount + 1, 0); + + // Initialize queue with nodes having in-degree 0 + eastl::vector queue; + queue.reserve(nodeCount); + for (std::uint32_t index = 0; index < nodeCount; ++index) + { + std::uint32_t degree = 0; + for (const NODE_TYPE& edge: p_graph.at(indexToNode[index])) + { + const auto nodeIt = nodeToIndex.find(edge); + if (nodeIt != nodeToIndex.end()) + { + ++degree; + ++adjacencyOffsets[nodeIt->second + 1]; + } + } + + inDegree[index] = degree; + if (degree == 0) + { + queue.push_back(index); + } + } + + for (std::uint32_t index = 0; index < nodeCount; ++index) + { + adjacencyOffsets[index + 1] += adjacencyOffsets[index]; + } + + eastl::vector adjacency(adjacencyOffsets.back()); + eastl::vector cursor(adjacencyOffsets.begin(), adjacencyOffsets.end() - 1); + for (std::uint32_t index = 0; index < nodeCount; ++index) + { + for (const NODE_TYPE& edge: p_graph.at(indexToNode[index])) + { + const auto nodeIt = nodeToIndex.find(edge); + if (nodeIt != nodeToIndex.end()) + { + adjacency[cursor[nodeIt->second]++] = index; + } + } + } + + // Step 2: Process nodes + std::size_t resolvedCount = 0; + eastl::vector nextQueue; + nextQueue.reserve(nodeCount); + while (!queue.empty()) + { + resolvedCount += queue.size(); + + // Reduce in-degree for neighbors + nextQueue.clear(); + for (const std::uint32_t index: queue) + { + for (std::uint32_t i = adjacencyOffsets[index]; i < adjacencyOffsets[index + 1]; ++i) + { + const std::uint32_t next = adjacency[i]; + if (--inDegree[next] == 0) + { + nextQueue.push_back(next); + } + } + } + + eastl::vector waveNodes; + waveNodes.reserve(queue.size()); + for (const std::uint32_t index: queue) + { + waveNodes.push_back(indexToNode[index]); + } + p_waves.push_back(std::move(waveNodes)); + + queue.swap(nextQueue); + } + + // Step 3 & 4: Check for valid topological order + if (resolvedCount != nodeCount) + { + p_cyclicNodes.reserve(nodeCount - resolvedCount); + for (std::uint32_t index = 0; index < nodeCount; ++index) + { + if (inDegree[index] != 0) + { + p_cyclicNodes.push_back(indexToNode[index]); + } + } + } + + return resolvedCount != nodeCount; +} +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Assert.hpp b/Bigfoot/Sources/Utils/Include/Utils/Assert.hpp deleted file mode 100644 index 4bdefc2..0000000 --- a/Bigfoot/Sources/Utils/Include/Utils/Assert.hpp +++ /dev/null @@ -1,94 +0,0 @@ -/********************************************************************* - * \file Assert.hpp - * - * \author Romain BOULLARD - * \date October 2025 - *********************************************************************/ -#ifndef BIGFOOT_UTILS_ASSERT_HPP -#define BIGFOOT_UTILS_ASSERT_HPP - -#if defined BIGFOOT_NOT_OPTIMIZED - -#include - -#include -#include - -#if defined BIGFOOT_LINUX -#include -#endif - -#if defined BIGFOOT_WINDOWS -#define BREAK \ - do \ - { \ - __debugbreak(); \ - } while (false) -#elif defined BIGFOOT_LINUX -#define BREAK \ - do \ - { \ - std::raise(SIGTRAP); \ - } while (false) -#endif - -#define ASSERT(HANDLER, p_assert, p_message, ...) \ - do \ - { \ - constexpr std::source_location location = std::source_location::current(); \ - if (!(p_assert)) [[unlikely]] \ - { \ - constexpr auto stacktrace = []() -> std::string \ - { \ - const cpptrace::stacktrace stacktrace = cpptrace::generate_trace(); \ - return stacktrace.to_string(); \ - }; \ - \ - HANDLER::Handle(location, stacktrace(), p_message __VA_OPT__(, ) __VA_ARGS__); \ - BREAK; \ - } \ - } while (false) - -#define SOFT_ASSERT(HANDLER, p_assert, p_message, ...) \ - do \ - { \ - constexpr std::source_location location = std::source_location::current(); \ - if (!(p_assert)) [[unlikely]] \ - { \ - constexpr auto stacktrace = []() -> std::string \ - { \ - const cpptrace::stacktrace stacktrace = cpptrace::generate_trace(); \ - return stacktrace.to_string(); \ - }; \ - \ - HANDLER::Handle(location, stacktrace(), p_message __VA_OPT__(, ) __VA_ARGS__); \ - BREAK; \ - } \ - } while (false) - -#define CRITICAL_ASSERT(HANDLER, p_assert, p_message, ...) \ - do \ - { \ - constexpr std::source_location location = std::source_location::current(); \ - if (!(p_assert)) [[unlikely]] \ - { \ - constexpr auto stacktrace = []() -> std::string \ - { \ - const cpptrace::stacktrace stacktrace = cpptrace::generate_trace(); \ - return stacktrace.to_string(); \ - }; \ - \ - HANDLER::Handle(location, stacktrace(), p_message __VA_OPT__(, ) __VA_ARGS__); \ - BREAK; \ - std::abort(); \ - } \ - } while (false) - -#else -#define ASSERT(HANDLER, p_assert, p_message, ...) - -#define SOFT_ASSERT(HANDLER, p_assert, p_message, ...) - -#define CRITICAL_ASSERT(HANDLER, p_assert, p_message, ...) -#endif -#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Assert/Assert.hpp b/Bigfoot/Sources/Utils/Include/Utils/Assert/Assert.hpp new file mode 100644 index 0000000..2da6ee6 --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Assert/Assert.hpp @@ -0,0 +1,112 @@ +/********************************************************************* + * \file Assert.hpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_ASSERT_HPP +#define BIGFOOT_UTILS_ASSERT_HPP +#include +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include + +#include +#include + +#include +#include + +#ifdef BIGFOOT_LINUX +#include +#endif + +#ifdef BIGFOOT_WINDOWS +#define BREAK \ + do \ + { \ + BIGFOOT_FLUSH_LOG; \ + __debugbreak(); \ + } while (false) +#elif defined BIGFOOT_LINUX +#define BREAK \ + do \ + { \ + BIGFOOT_FLUSH_LOG; \ + std::raise(SIGTRAP); \ + } while (false) +#endif + +namespace Bigfoot +{ +/** + * Assert then break + * + * \param p_condition The condition to check + * \param p_message A function that will return the formatting string describing the issue + * \param p_location The location the assert originates from + */ +template +BIGFOOT_ALWAYS_INLINE void AssertCheck(const bool p_condition, + const eastl::fixed_function<32, eastl::string()>& p_messageFn, + const std::source_location p_location = std::source_location::current()) +{ + if (!p_condition) [[unlikely]] + { + HANDLER::Handle(p_location, p_messageFn()); + BREAK; + } +} + +/** + * Assert then break then kill + * + * \param p_condition The condition to check + * \param p_message A function that will return the formatting string describing the issue + * \param p_location The location the assert originates from + */ +template +BIGFOOT_ALWAYS_INLINE void CriticalAssertCheck(const bool p_condition, + const eastl::fixed_function<32, eastl::string()>& p_messageFn, + const std::source_location p_location = std::source_location::current()) +{ + if (!p_condition) [[unlikely]] + { + HANDLER::Handle(p_location, p_messageFn()); + BREAK; + std::abort(); + } +} +} // namespace Bigfoot + +#define ASSERT(HANDLER, p_assert, p_message, ...) \ + ::Bigfoot::AssertCheck((p_assert), \ + [&] \ + { \ + return ::Bigfoot::Format(p_message __VA_OPT__(, ) __VA_ARGS__); \ + }) + +#define SOFT_ASSERT(HANDLER, p_assert, p_message, ...) \ + ::Bigfoot::AssertCheck((p_assert), \ + [&] \ + { \ + return ::Bigfoot::Format(p_message __VA_OPT__(, ) __VA_ARGS__); \ + }) + +#define CRITICAL_ASSERT(HANDLER, p_assert, p_message, ...) \ + ::Bigfoot::CriticalAssertCheck((p_assert), \ + [&] \ + { \ + return ::Bigfoot::Format(p_message __VA_OPT__(, ) __VA_ARGS__); \ + }) + +#else +#define ASSERT(HANDLER, p_assert, p_message, ...) + +#define SOFT_ASSERT(HANDLER, p_assert, p_message, ...) + +#define CRITICAL_ASSERT(HANDLER, p_assert, p_message, ...) +#endif +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Assert/AssertHandler.hpp b/Bigfoot/Sources/Utils/Include/Utils/Assert/AssertHandler.hpp new file mode 100644 index 0000000..e772d71 --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Assert/AssertHandler.hpp @@ -0,0 +1,52 @@ +/********************************************************************* + * \file AssertHandler.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_ASSERTHANDLER_HPP +#define BIGFOOT_UTILS_ASSERTHANDLER_HPP +#include +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include + +#include + +namespace Bigfoot +{ +template +class AssertHandler +{ + public: + AssertHandler() = delete; + + AssertHandler(const AssertHandler& p_handler) = delete; + AssertHandler(AssertHandler&& p_handler) = delete; + + ~AssertHandler() = delete; + + /** + * Handle an assertion. + * + * \param p_location Location of the assertion. + * \param p_message Message describing the assertion failure. + */ + static void Handle(const std::source_location& p_location, const eastl::string& p_message) + { + BIGFOOT_LOG_FATAL(LOGGER, + "Assert: {} (File:{}, Line:{}, Function:{}\n", + p_message, + p_location.file_name(), + p_location.line(), + p_location.function_name()); + } + + AssertHandler& operator=(const AssertHandler& p_handler) = delete; + AssertHandler& operator=(AssertHandler&& p_handler) = delete; +}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp new file mode 100644 index 0000000..be8074f --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp @@ -0,0 +1,1044 @@ +/********************************************************************* + * \file SlotMap.hpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_CONTAINERS_SLOTMAP_HPP +#define BIGFOOT_UTILS_CONTAINERS_SLOTMAP_HPP +#include + +#include +#include +#include +#include +#include + +#include + +namespace Bigfoot +{ +template +class SlotMapKey +{ + public: + using IndexType = INDEX_TYPE; + using VersionType = VERSION_TYPE; + + private: + static constexpr std::uint32_t VERSION_BIT_COUNT = sizeof(VersionType) * std::numeric_limits::digits; + static constexpr std::uint32_t INDEX_BIT_COUNT = sizeof(IndexType) * std::numeric_limits::digits; + static_assert(VERSION_BIT_COUNT + INDEX_BIT_COUNT <= std::numeric_limits::digits, + "We cant construct a 64 bit key from the given Version and Index types!"); + + static constexpr std::uint64_t INDEX_MASK = (static_cast(1) << INDEX_BIT_COUNT) - 1; + + public: + static constexpr VersionType INVALID_VERSION = 0; + + static constexpr IndexType MAX_INDEX = std::numeric_limits::max(); + static constexpr VersionType MAX_VERSION = std::numeric_limits::max(); + + /** + * Constructor + * + * \param p_version The version + * \param p_index The index + */ + constexpr SlotMapKey(const VersionType p_version, const IndexType p_index): + m_key((static_cast(p_version) << INDEX_BIT_COUNT) | + (static_cast(p_index) & INDEX_MASK)) + { + } + + /** + * Constructor + * + * Constructs an invalid key + */ + constexpr SlotMapKey(): + SlotMapKey(INVALID_VERSION, 0) + { + } + + /** + * Check if the key is valid (Version different than INVALID_VERSION) + * + * \return True if valid, false otherwise + */ + [[nodiscard]] + constexpr bool Valid() const + { + return GetVersion() != INVALID_VERSION; + } + + /** + * Get the version + * + * \return The version + */ + [[nodiscard]] + constexpr VersionType GetVersion() const + { + return static_cast(m_key >> INDEX_BIT_COUNT); + } + + /** + * Get the index + * + * \return The index + */ + [[nodiscard]] + constexpr IndexType GetIndex() const + { + return static_cast(m_key & INDEX_MASK); + } + + [[nodiscard]] + constexpr bool operator==(const SlotMapKey& p_other) const = default; + + private: + /** + * The key => (VERSION | INDEX) + */ + std::uint64_t m_key; +}; + +/** + * Storage is dense + * Pointer stability not guarantee + * Fast iteration + */ +template> +class DenseSlotMap +{ + public: + using SlotMapKey = SLOT_MAP_KEY_TYPE; + + using iterator = eastl::vector::iterator; + using const_iterator = eastl::vector::const_iterator; + using reverse_iterator = eastl::vector::reverse_iterator; + using const_reverse_iterator = eastl::vector::const_reverse_iterator; + + /** + * Check if the map has the key + * + * \param p_key The key to check + * \return True if it has the key, false otherwise + */ + [[nodiscard]] + bool Has(const SlotMapKey p_key) const + { + return p_key.Valid() && + (p_key.GetIndex() < m_keys.size() && p_key.GetVersion() == m_keys[p_key.GetIndex()].GetVersion()); + } + + /** + * Get the size of the map + * + * \return The size of the map + */ + [[nodiscard]] + std::size_t Size() const + { + return m_data.size(); + } + + /** + * Get the capacity of the map + * + * \return The capacity of the map + */ + [[nodiscard]] + std::size_t Capacity() const + { + return m_data.capacity(); + } + + /** + * Check if the map is empty + * + * \return True if empty, false otherwise + */ + [[nodiscard]] + bool Empty() const + { + return m_data.empty(); + } + + /** + * Insert to the slotmap + * + * \tparam Templated arguments + * \param p_args The thing to insert, arguments will be forwarded + * \return A key to the inserted element + */ + template + [[nodiscard]] + SlotMapKey Insert(ARGS&&... p_args) + { + ASSERT(UtilsAssertHandler, + Size() < SlotMapKey::MAX_INDEX, + "Can't store more than {} elements", + SlotMapKey::MAX_INDEX); + + m_data.emplace_back(std::forward(p_args)...); + const std::size_t indexToInsertedData = m_data.size() - 1; + + if (HasFreeSlots()) + { + const std::size_t indexToRecyclingKey = m_freeListHead; + + m_freeListHead = m_keys[indexToRecyclingKey].GetIndex(); + m_keys[indexToRecyclingKey] = SlotMapKey {m_keys[indexToRecyclingKey].GetVersion(), + static_cast(indexToInsertedData)}; + m_dataToKey.emplace_back(indexToRecyclingKey); + + return SlotMapKey {m_keys[indexToRecyclingKey].GetVersion(), + static_cast(indexToRecyclingKey)}; + } + + m_keys.emplace_back(1, static_cast(indexToInsertedData)); + m_dataToKey.push_back(m_keys.size() - 1); + + return SlotMapKey {1, static_cast(m_keys.size() - 1)}; + } + + /** + * Remove an element from the map + * + * \param p_key The element to remove + */ + void Remove(const SlotMapKey p_key) + { + if (!Has(p_key)) + { + return; + } + + const std::size_t dataIndex = GetDataIndex(p_key); + + m_data.erase_unsorted(m_data.begin() + dataIndex); + m_dataToKey.erase_unsorted(m_dataToKey.begin() + dataIndex); + + if (dataIndex < Size()) + { + m_keys[m_dataToKey[dataIndex]] = SlotMapKey {m_keys[m_dataToKey[dataIndex]].GetVersion(), + static_cast(dataIndex)}; + } + + RecycleKey(p_key); + } + + /** + * Reset the slotmap + * Everything will be cleared + * Old keys are not stale + * + */ + void Reset() + { + m_data.clear(); + m_dataToKey.clear(); + m_keys.clear(); + m_freeListHead = SlotMapKey::MAX_INDEX; + } + + /** + * Clear the slotmap + * Data is lost + * Old keys are stale + * + */ + void Clear() + { + for (const std::size_t keyIndex: m_dataToKey) + { + RecycleKey(SlotMapKey {m_keys[keyIndex].GetVersion(), static_cast(keyIndex)}); + } + + m_data.clear(); + m_dataToKey.clear(); + } + + /** + * Get the element at p_slotKey + * + * \param p_slotKey The key to get + * \return Pointer to the element, nullptr if p_slotKey is not in the map + */ + [[nodiscard]] + T* Get(const SlotMapKey p_slotKey) + { + return Has(p_slotKey) ? &m_data[GetDataIndex(p_slotKey)] : nullptr; + } + + /** + * Get the element at p_slotKey + * + * \param p_slotKey The key to get + * \return Pointer to the element, nullptr if p_slotKey is not in the map + */ + [[nodiscard]] + const T* Get(const SlotMapKey p_slotKey) const + { + return Has(p_slotKey) ? &m_data[GetDataIndex(p_slotKey)] : nullptr; + } + + [[nodiscard]] + iterator begin() + { + return m_data.begin(); + } + + [[nodiscard]] + iterator end() + { + return m_data.end(); + } + + [[nodiscard]] + const_iterator begin() const + { + return m_data.begin(); + } + + [[nodiscard]] + const_iterator end() const + { + return m_data.end(); + } + + [[nodiscard]] + reverse_iterator rbegin() + { + return m_data.rbegin(); + } + + [[nodiscard]] + reverse_iterator rend() + { + return m_data.rend(); + } + + [[nodiscard]] + const_reverse_iterator rbegin() const + { + return m_data.rbegin(); + } + + [[nodiscard]] + const_reverse_iterator rend() const + { + return m_data.rend(); + } + + [[nodiscard]] + const_iterator cbegin() const + { + return m_data.cbegin(); + } + + [[nodiscard]] + const_iterator cend() const + { + return m_data.cend(); + } + + [[nodiscard]] + const_reverse_iterator crbegin() const + { + return m_data.crbegin(); + } + + [[nodiscard]] + const_reverse_iterator crend() const + { + return m_data.crend(); + } + + private: + /** + * Does the map have free slots (ie: indices that can be recyled) + * + * \return True if indices can be recycled, false otherwise + */ + [[nodiscard]] + bool HasFreeSlots() const + { + return m_freeListHead != SlotMapKey::MAX_INDEX; + } + + /** + * The the index of the data in the contiguous array + * + * \param p_key The key of the element to get the index of + * \return The index of the element in the contiguous array + */ + [[nodiscard]] + std::size_t GetDataIndex(const SlotMapKey p_key) const + { + return m_keys[p_key.GetIndex()].GetIndex(); + } + + /** + * Recycle a key + * + * \param p_key The key to recycle + */ + void RecycleKey(const SlotMapKey p_key) + { + const typename SlotMapKey::VersionType nextVersion = static_cast(p_key.GetVersion() + + 1); + + if (nextVersion == SlotMapKey::INVALID_VERSION) + { + // Mark it dead, we've exhausted all versions for this key + m_keys[p_key.GetIndex()] = SlotMapKey {SlotMapKey::INVALID_VERSION, 0}; + return; + } + + m_keys[p_key.GetIndex()] = SlotMapKey {nextVersion, static_cast(m_freeListHead)}; + m_freeListHead = p_key.GetIndex(); + } + + /** + * The actual storage for our data + */ + eastl::vector m_data; + /** + * The keys containing the indices to our data + */ + eastl::vector m_keys; + + /** + * Allows us to match m_data and m_keys. + */ + eastl::vector m_dataToKey; + + /** + * Index to a key that can be recycled + */ + std::size_t m_freeListHead = SlotMapKey::MAX_INDEX; +}; + +/** + * Storage is not dense + * Pointer stability is guarantee + * Slower iteration, fast insert/remove/random access + */ +template> +class StableSlotMap +{ + public: + static constexpr std::size_t PAGE_SIZE = 64; + + private: + struct AlignedSlot + { + alignas(T) eastl::array m_bytes; + }; + + struct Page + { + Page() = default; + + Page(const Page& p_page) = delete; + Page(Page&& p_page) = delete; + + ~Page() + { + for (std::size_t offset = m_occupied.find_first(); offset != PAGE_SIZE; + offset = m_occupied.find_next(offset)) + { + std::destroy_at(Get(offset)); + } + } + + Page& operator=(const Page& p_page) = delete; + Page& operator=(Page&& p_page) = delete; + + /** + * Get the data at the specified offset + * + * \return Pointer to the data + */ + [[nodiscard]] + T* Get(const std::size_t p_offset) + { + ASSERT(UtilsAssertHandler, m_occupied[p_offset], "Requesting a dead data!"); + return std::launder(reinterpret_cast(m_data[p_offset].m_bytes.data())); + } + + /** + * Get the data at the specified offset + * + * \return Pointer to the data + */ + [[nodiscard]] + const T* Get(const std::size_t p_offset) const + { + ASSERT(UtilsAssertHandler, m_occupied[p_offset], "Requesting a dead data!"); + return std::launder(reinterpret_cast(m_data[p_offset].m_bytes.data())); + } + + /** + * Get the size of the page + * + * \return The size of the page + */ + [[nodiscard]] + std::size_t Size() const + { + return m_occupied.count(); + } + + /** + * The data + */ + eastl::array m_data; + /** + * Bitset representing which indices are alive or not + */ + eastl::bitset m_occupied; + }; + + public: + using SlotMapKey = SLOT_MAP_KEY_TYPE; + + template + class IteratorImpl + { + private: + using PagesVector = eastl::vector>; + using PageIterator = std::conditional_t, + typename PagesVector::const_iterator, + typename PagesVector::iterator>; + using MapType = std::conditional_t, const StableSlotMap, StableSlotMap>; + using PageType = std::conditional_t, const Page, Page>; + + public: + using iterator_category = eastl::bidirectional_iterator_tag; + using value_type = std::remove_const_t; + using difference_type = std::ptrdiff_t; + using pointer = VALUE_TYPE*; + using reference = VALUE_TYPE&; + + IteratorImpl() = default; + + // Allows an iterator to be implicitly used as a const_iterator. + template + requires std::is_same_v + IteratorImpl(const IteratorImpl& p_other): + m_map(p_other.m_map), + m_pageIt(p_other.m_pageIt), + m_page(p_other.m_page), + m_offset(p_other.m_offset), + m_current(p_other.m_current) + { + } + + [[nodiscard]] + reference operator*() const + { + return *m_current; + } + + [[nodiscard]] + pointer operator->() const + { + return m_current; + } + + IteratorImpl& operator++() + { + Increment(); + return *this; + } + + IteratorImpl operator++(int) + { + const IteratorImpl temp = *this; + ++(*this); + return temp; + } + + IteratorImpl& operator--() + { + Decrement(); + return *this; + } + + IteratorImpl operator--(int) + { + const IteratorImpl temp = *this; + --(*this); + return temp; + } + + [[nodiscard]] + bool operator==(const IteratorImpl& p_other) const + { + return m_map == p_other.m_map && m_current == p_other.m_current; + } + + private: + friend class StableSlotMap; + template + friend class IteratorImpl; + + explicit IteratorImpl(MapType* p_map): + m_map(p_map) + { + } + + void SeekFirstFrom(PageIterator p_pageIt) + { + for (; p_pageIt != m_map->m_pages.end(); ++p_pageIt) + { + PageType* const page = p_pageIt->get(); + const std::size_t offset = page->m_occupied.find_first(); + if (offset != PAGE_SIZE) + { + m_pageIt = p_pageIt; + m_page = page; + m_offset = offset; + m_current = page->Get(offset); + return; + } + } + + m_pageIt = p_pageIt; + m_page = nullptr; + m_offset = 0; + m_current = nullptr; + } + + void SeekLastFrom(PageIterator p_pageIt) + { + for (;;) + { + PageType* const page = p_pageIt->get(); + const std::size_t offset = page->m_occupied.find_last(); + if (offset != PAGE_SIZE) + { + m_pageIt = p_pageIt; + m_page = page; + m_offset = offset; + m_current = page->Get(offset); + return; + } + + if (p_pageIt == m_map->m_pages.begin()) + { + return; + } + --p_pageIt; + } + } + + void Increment() + { + const std::size_t offset = m_page->m_occupied.find_next(m_offset); + if (offset != PAGE_SIZE) + { + m_offset = offset; + m_current = m_page->Get(offset); + return; + } + + SeekFirstFrom(eastl::next(m_pageIt)); + } + + void Decrement() + { + if (m_current == nullptr) + { + SeekLastFrom(eastl::prev(m_map->m_pages.end())); + return; + } + + const std::size_t offset = m_page->m_occupied.find_prev(m_offset); + if (offset != PAGE_SIZE) + { + m_offset = offset; + m_current = m_page->Get(offset); + return; + } + + if (m_pageIt == m_map->m_pages.begin()) + { + return; + } + SeekLastFrom(eastl::prev(m_pageIt)); + } + + MapType* m_map = nullptr; + PageIterator m_pageIt {}; + PageType* m_page = nullptr; + std::size_t m_offset = 0; + pointer m_current = nullptr; + }; + + using iterator = IteratorImpl; + using const_iterator = IteratorImpl; + using reverse_iterator = eastl::reverse_iterator; + using const_reverse_iterator = eastl::reverse_iterator; + + /** + * Check if the map has the key + * + * \param p_key The key to check + * \return True if it has the key, false otherwise + */ + [[nodiscard]] + bool Has(const SlotMapKey p_key) const + { + return p_key.Valid() && + (p_key.GetIndex() < m_keys.size() && p_key.GetVersion() == m_keys[p_key.GetIndex()].GetVersion()); + } + + /** + * Get the size of the map + * + * \return The size of the map + */ + [[nodiscard]] + std::size_t Size() const + { + return eastl::accumulate(m_pages.begin(), + m_pages.end(), + 0, + [](const std::size_t p_value, const eastl::unique_ptr& p_page) + { + return p_value + p_page->Size(); + }); + } + + /** + * Get the capacity of the map + * + * \return The capacity of the map + */ + [[nodiscard]] + std::size_t Capacity() const + { + return m_pages.size() * PAGE_SIZE; + } + + /** + * Check if the map is empty + * + * \return True if empty, false otherwise + */ + [[nodiscard]] + bool Empty() const + { + return Size() == 0; + } + + /** + * Insert to the slotmap + * + * \tparam Templated arguments + * \param p_args The thing to insert, arguments will be forwarded + * \return A key to the inserted element + */ + template + [[nodiscard]] + SlotMapKey Insert(ARGS&&... p_args) + { + ASSERT(UtilsAssertHandler, + Size() < SlotMapKey::MAX_INDEX, + "Can't store more than {} elements", + SlotMapKey::MAX_INDEX); + + SlotMapKey key; + if (HasFreeSlots()) + { + const std::size_t indexToRecyclingKey = m_freeListHead; + + m_freeListHead = m_keys[indexToRecyclingKey].GetIndex(); + m_keys[indexToRecyclingKey] = SlotMapKey {m_keys[indexToRecyclingKey].GetVersion(), + static_cast(indexToRecyclingKey)}; + + key = m_keys[indexToRecyclingKey]; + } + else + { + m_keys.emplace_back(1, static_cast(m_keys.size())); + key = m_keys.back(); + } + + const std::size_t pageIndex = GetPageIndex(key); + if (pageIndex >= m_pages.size()) + { + m_pages.push_back(eastl::make_unique()); + } + + Page* const page = GetPageAt(pageIndex); + const std::size_t offsetInPage = GetDataOffset(key); + new (page->m_data[offsetInPage].m_bytes.data()) T(std::forward(p_args)...); + page->m_occupied.set(offsetInPage); + + return key; + } + + /** + * Remove an element from the map + * + * \param p_key The element to remove + */ + void Remove(const SlotMapKey p_key) + { + if (!Has(p_key)) + { + return; + } + + Page* const page = GetPageAt(GetPageIndex(p_key)); + const std::size_t offsetInPage = GetDataOffset(p_key); + std::destroy_at(page->Get(offsetInPage)); + page->m_occupied.reset(offsetInPage); + + RecycleKey(p_key); + } + + /** + * Reset the slotmap + * Everything will be cleared + * Old keys are not stale + * + */ + void Reset() + { + for (eastl::unique_ptr& page: m_pages) + { + page = eastl::make_unique(); + } + m_keys.clear(); + m_freeListHead = SlotMapKey::MAX_INDEX; + } + + /** + * Clear the slotmap + * Data is lost + * Old keys are stale + * + */ + void Clear() + { + std::size_t pageIndex = 0; + for (const eastl::unique_ptr& page: m_pages) + { + for (std::size_t offset = page->m_occupied.find_first(); offset != PAGE_SIZE; + offset = page->m_occupied.find_next(offset)) + { + std::destroy_at(page->Get(offset)); + + const std::size_t keyIndex = (pageIndex * PAGE_SIZE) + offset; + RecycleKey(SlotMapKey {m_keys[keyIndex].GetVersion(), static_cast(keyIndex)}); + } + page->m_occupied.reset(); + ++pageIndex; + } + } + + /** + * Get the element at p_slotKey + * + * \param p_slotKey The key to get + * \return Pointer to the element, nullptr if p_slotKey is not in the map + */ + [[nodiscard]] + const T* Get(const SlotMapKey p_key) const + { + return Has(p_key) ? GetPageAt(GetPageIndex(p_key))->Get(GetDataOffset(p_key)) : nullptr; + } + + /** + * Get the element at p_slotKey + * + * \param p_slotKey The key to get + * \return Pointer to the element, nullptr if p_slotKey is not in the map + */ + [[nodiscard]] + T* Get(const SlotMapKey p_key) + { + return Has(p_key) ? GetPageAt(GetPageIndex(p_key))->Get(GetDataOffset(p_key)) : nullptr; + } + + [[nodiscard]] + iterator begin() + { + iterator result {this}; + result.SeekFirstFrom(m_pages.begin()); + return result; + } + + [[nodiscard]] + iterator end() + { + return iterator {this}; + } + + [[nodiscard]] + const_iterator begin() const + { + const_iterator result {this}; + result.SeekFirstFrom(m_pages.begin()); + return result; + } + + [[nodiscard]] + const_iterator end() const + { + return const_iterator {this}; + } + + [[nodiscard]] + reverse_iterator rbegin() + { + return reverse_iterator {end()}; + } + + [[nodiscard]] + reverse_iterator rend() + { + return reverse_iterator {begin()}; + } + + [[nodiscard]] + const_reverse_iterator rbegin() const + { + return const_reverse_iterator {end()}; + } + + [[nodiscard]] + const_reverse_iterator rend() const + { + return const_reverse_iterator {begin()}; + } + + [[nodiscard]] + const_iterator cbegin() const + { + return begin(); + } + + [[nodiscard]] + const_iterator cend() const + { + return end(); + } + + [[nodiscard]] + const_reverse_iterator crbegin() const + { + return rbegin(); + } + + [[nodiscard]] + const_reverse_iterator crend() const + { + return rend(); + } + + private: + /** + * Does the map have free slots (ie: indices that can be recyled) + * + * \return True if indices can be recycled, false otherwise + */ + [[nodiscard]] + bool HasFreeSlots() const + { + return m_freeListHead != SlotMapKey::MAX_INDEX; + } + + /** + * Recycle a key + * + * \param p_key The key to recycle + */ + void RecycleKey(const SlotMapKey p_key) + { + const typename SlotMapKey::VersionType nextVersion = static_cast(p_key.GetVersion() + + 1); + + if (nextVersion == SlotMapKey::INVALID_VERSION) + { + // Mark it dead, we've exhausted all versions for this key + m_keys[p_key.GetIndex()] = SlotMapKey {SlotMapKey::INVALID_VERSION, 0}; + return; + } + + m_keys[p_key.GetIndex()] = SlotMapKey {nextVersion, static_cast(m_freeListHead)}; + m_freeListHead = p_key.GetIndex(); + } + + /** + * Given a key, get the index of its page + * + * \param p_key The key + * \return The index of the page + */ + [[nodiscard]] + static std::size_t GetPageIndex(const SlotMapKey p_key) + { + return p_key.GetIndex() / PAGE_SIZE; + } + + /** + * Given a key, get the offset of the data inside its page + * + * \param p_key The key + * \return The offset the of data + */ + [[nodiscard]] + static std::size_t GetDataOffset(const SlotMapKey p_key) + { + return p_key.GetIndex() % PAGE_SIZE; + } + + /** + * Get the page + * + * \param p_pageIndex The idnex to get + * \return The page + */ + [[nodiscard]] + const Page* GetPageAt(const std::size_t p_pageIndex) const + { + return m_pages[p_pageIndex].get(); + } + + /** + * Get the page + * + * \param p_pageIndex The idnex to get + * \return The page + */ + [[nodiscard]] + Page* GetPageAt(const std::size_t p_pageIndex) + { + return m_pages[p_pageIndex].get(); + } + + /** + * The pages + */ + eastl::vector> m_pages; + /** + * The keys containing the indices to our data + */ + eastl::vector m_keys; + + /** + * Index to a key that can be recycled + */ + std::size_t m_freeListHead = SlotMapKey::MAX_INDEX; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Format.hpp b/Bigfoot/Sources/Utils/Include/Utils/Format.hpp new file mode 100644 index 0000000..65474be --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Format.hpp @@ -0,0 +1,31 @@ +/********************************************************************* + * \file Format.hpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_FORMAT_HPP +#define BIGFOOT_UTILS_FORMAT_HPP +#include +#include + +#include + +namespace Bigfoot +{ +/** + * Format a string, like fmt::format, but returning an eastl::string instead of a std::string. + * + * \param p_format The format string + * \param p_args The arguments for the format string + * \return The formatted eastl::string + */ +template +eastl::string Format(fmt::format_string p_format, ARGS&&... p_args) +{ + fmt::memory_buffer buffer; + fmt::format_to(std::back_inserter(buffer), p_format, std::forward(p_args)...); + return eastl::string {buffer.data(), buffer.size()}; +} +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Log/EASTLFormatters.hpp b/Bigfoot/Sources/Utils/Include/Utils/Log/EASTLFormatters.hpp new file mode 100644 index 0000000..153e15f --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/EASTLFormatters.hpp @@ -0,0 +1,175 @@ +/********************************************************************* + * \file EASTLFormatters.hpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_LOG_EASTLFORMATTERS_HPP +#define BIGFOOT_UTILS_LOG_EASTLFORMATTERS_HPP +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED +#include +#endif + +#include +#include +#include +#include + +#include + +// STRING + +template<> +struct fmt::formatter +{ + static constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + static auto format(const eastl::string& p_string, format_context& ctx) + { + return fmt::format_to(ctx.out(), "{}", p_string.c_str()); + } +}; + +#ifdef BIGFOOT_NOT_OPTIMIZED +template<> +struct fmtquill::formatter +{ + static constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + static auto format(const eastl::string& p_string, format_context& ctx) + { + return fmtquill::format_to(ctx.out(), "{}", p_string.c_str()); + } +}; +#endif + +// STRING_VIEW + +template<> +struct fmt::formatter +{ + static constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + static auto format(const eastl::string_view& p_stringView, format_context& ctx) + { + return fmt::format_to(ctx.out(), "{}", p_stringView.data()); + } +}; + +#ifdef BIGFOOT_NOT_OPTIMIZED +template<> +struct fmtquill::formatter +{ + static constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + static auto format(const eastl::string_view& p_stringView, format_context& ctx) + { + return fmtquill::format_to(ctx.out(), "{}", p_stringView.data()); + } +}; + +namespace Bigfoot::Codec +{ +template +struct string_view: quill::Codec +{ + using Base = quill::Codec; + + [[nodiscard]] + static size_t compute_encoded_size(quill::detail::SizeCacheVector& p_cache, const STRING& p_arg) noexcept + { + return Base::compute_encoded_size(p_cache, std::string_view {p_arg.data(), p_arg.size()}); + } + + static void encode(std::byte*& p_buffer, + const quill::detail::SizeCacheVector& p_cache, + std::uint32_t& p_cacheIndex, + const STRING& p_arg) noexcept + { + Base::encode(p_buffer, p_cache, p_cacheIndex, std::string_view {p_arg.data(), p_arg.size()}); + } +}; +} // namespace Bigfoot::Codec + +template<> +struct quill::Codec: Bigfoot::Codec::string_view +{ +}; + +template<> +struct quill::Codec: Bigfoot::Codec::string_view +{ +}; +#endif + +// VECTOR + +template +struct fmt::formatter> +{ + constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const eastl::vector& p_vector, format_context& ctx) const + { + auto out = fmt::format_to(ctx.out(), "["); + for (std::size_t i = 0; i < p_vector.size(); ++i) + { + if (i > 0) + { + out = fmt::format_to(out, ", "); + } + out = fmt::format_to(out, "{}", p_vector[i]); + } + return fmt::format_to(out, "]"); + } +}; + +#ifdef BIGFOOT_NOT_OPTIMIZED +#include + +template +struct fmtquill::formatter> +{ + constexpr auto parse(format_parse_context& ctx) + { + return ctx.begin(); + } + + auto format(const eastl::vector& p_vector, format_context& ctx) const + { + auto out = fmtquill::format_to(ctx.out(), "["); + for (std::size_t i = 0; i < p_vector.size(); ++i) + { + if (i > 0) + { + out = fmtquill::format_to(out, ", "); + } + out = fmtquill::format_to(out, "{}", p_vector[i]); + } + return fmtquill::format_to(out, "]"); + } +}; + +template +struct quill::Codec>: quill::DeferredFormatCodec> +{ +}; +#endif +#endif diff --git a/Bigfoot/Sources/System/Include/System/Log/Log.fbs b/Bigfoot/Sources/Utils/Include/Utils/Log/Log.fbs similarity index 86% rename from Bigfoot/Sources/System/Include/System/Log/Log.fbs rename to Bigfoot/Sources/Utils/Include/Utils/Log/Log.fbs index 727798b..be9ae90 100644 --- a/Bigfoot/Sources/System/Include/System/Log/Log.fbs +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/Log.fbs @@ -2,7 +2,8 @@ namespace Bigfoot.Flat; enum LogSinkType: byte { - Console + Console, + File } enum LogLevel: byte diff --git a/Bigfoot/Sources/Utils/Include/Utils/Log/Log.hpp b/Bigfoot/Sources/Utils/Include/Utils/Log/Log.hpp new file mode 100644 index 0000000..5a77d0e --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/Log.hpp @@ -0,0 +1,109 @@ +/********************************************************************* + * \file Log.hpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_LOG_LOG_HPP +#define BIGFOOT_UTILS_LOG_LOG_HPP +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED +#include + +#include +#include + +#ifdef BIGFOOT_WINDOWS +#pragma warning(disable: 4702) +#endif +#include +#include +#include +#include +#include +#include +#ifdef BIGFOOT_WINDOWS +#pragma warning(default: 4702) +#endif + +namespace Bigfoot +{ +class Log +{ + public: + struct LoggerInfo + { + std::string m_name; + Flat::LogLevel m_level; + }; + + /** + * The path of the file the File sink writes to + */ + static constexpr eastl::string_view FILE_SINK_PATH = "Bigfoot.log"; + + Log(); + + Log(const Log& p_logger) = delete; + Log(Log&& p_logger) = delete; + + /** + * Register a logger. + * + * \param p_loggerInfo The logger to register + */ + [[nodiscard]] + quill::Logger* RegisterLogger(const LoggerInfo& p_loggerInfo); + + /** + * Unregister a logger. + * + * \param p_loggerInfo The logger to unregister + */ + static void UnregisterLogger(const LoggerInfo& p_loggerInfo); + + /** + * Register a logger. + * + * \param p_loggerInfo The logger to get + * \return The logger, nullptr if it does not exist + */ + [[nodiscard]] + quill::Logger* GetLogger(const LoggerInfo& p_loggerInfo); + + /** + * Changes the loglevel of a Logger. + * + * \param p_loggerInfo The logger to change + * \param p_level The new level + */ + void ChangeLoggerLogLevel(LoggerInfo& p_loggerInfo, const Flat::LogLevel p_level); + + /* + * Flush all the loggers + * + */ + static void Flush(); + + ~Log(); + + Log& operator=(const Log& p_logger) = delete; + Log& operator=(Log&& p_logger) = delete; + + private: + /** + * Set the LogLevel of a logger + * + * \param p_loggerInfo The logger to set + */ + void SetLoggerLevel(const LoggerInfo& p_loggerInfo); + + /* + * The sinks + */ + eastl::array, 2> m_sinks; +}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Log/LogMacros.hpp b/Bigfoot/Sources/Utils/Include/Utils/Log/LogMacros.hpp new file mode 100644 index 0000000..2ec7a1f --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/LogMacros.hpp @@ -0,0 +1,176 @@ +/********************************************************************* + * \file LogMacros.hpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_LOG_LOGMACROS_HPP +#define BIGFOOT_UTILS_LOG_LOGMACROS_HPP +#include +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED +#define BIGFOOT_FLUSH_LOG \ + do \ + { \ + if (Bigfoot::Singleton::HasInstance()) \ + { \ + Bigfoot::Singleton::GetInstance().Flush(); \ + } \ + } while (false) + +#define BIGFOOT_LOG_DEBUG(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_DEBUG(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_DEBUG(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_TRACE(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_TRACE_L3(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_TRACE_L3(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_INFO(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_INFO(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_INFO(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_WARN(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_WARNING(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_WARNING(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_ERROR(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_ERROR(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_ERROR(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_FATAL(loggerName, fmt, ...) \ + do \ + { \ + if (::quill::Logger* logger = ::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().GetLogger(loggerName)) \ + { \ + QUILL_LOG_CRITICAL(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + else \ + { \ + QUILL_LOG_CRITICAL(::Bigfoot::Singleton<::Bigfoot::Log>::GetInstance().RegisterLogger(loggerName), \ + fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) +#else +#define BIGFOOT_LOG_DEBUG(loggerName, fmt, ...) +#define BIGFOOT_LOG_TRACE(loggerName, fmt, ...) +#define BIGFOOT_LOG_INFO(loggerName, fmt, ...) +#define BIGFOOT_LOG_WARN(loggerName, fmt, ...) +#define BIGFOOT_LOG_ERROR(loggerName, fmt, ...) +#define BIGFOOT_LOG_FATAL(loggerName, fmt, ...) +#endif + +#ifdef BIGFOOT_NOT_OPTIMIZED +#define BIGFOOT_LOG_DEBUG_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_DEBUG(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_TRACE_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_TRACE(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_INFO_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_INFO(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_WARN_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_WARN(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_ERROR_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_ERROR(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) + +#define BIGFOOT_LOG_FATAL_IF(condition, loggerName, fmt, ...) \ + do \ + { \ + if (condition) \ + { \ + BIGFOOT_LOG_FATAL(loggerName, fmt __VA_OPT__(, ) __VA_ARGS__); \ + } \ + } while (0) +#else +#define BIGFOOT_LOG_DEBUG_IF(condition, loggerName, fmt, ...) +#define BIGFOOT_LOG_TRACE_IF(condition, loggerName, fmt, ...) +#define BIGFOOT_LOG_INFO_IF(condition, loggerName, fmt, ...) +#define BIGFOOT_LOG_WARN_IF(condition, loggerName, fmt, ...) +#define BIGFOOT_LOG_ERROR_IF(condition, loggerName, fmt, ...) +#define BIGFOOT_LOG_FATAL_IF(condition, loggerName, fmt, ...) +#endif +#endif diff --git a/Bigfoot/Sources/System/Include/System/Log/Log_generated.hpp b/Bigfoot/Sources/Utils/Include/Utils/Log/Log_generated.hpp similarity index 79% rename from Bigfoot/Sources/System/Include/System/Log/Log_generated.hpp rename to Bigfoot/Sources/Utils/Include/Utils/Log/Log_generated.hpp index 016228b..5cdcc17 100644 --- a/Bigfoot/Sources/System/Include/System/Log/Log_generated.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/Log_generated.hpp @@ -12,36 +12,47 @@ static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && FLATBUFFERS_VERSION_MINOR == 12 && FLATBUFFERS_VERSION_REVISION == 19, "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); #include "EASTL/unique_ptr.h" #include "EASTL/string.h" +#include "EASTL/vector.h" namespace Bigfoot { namespace Flat { enum class LogSinkType : int8_t { Console = 0, + File = 1, MIN = Console, - MAX = Console + MAX = File }; -inline const LogSinkType (&EnumValuesLogSinkType())[1] { +inline const LogSinkType (&EnumValuesLogSinkType())[2] { static const LogSinkType values[] = { - LogSinkType::Console + LogSinkType::Console, + LogSinkType::File }; return values; } inline const char * const *EnumNamesLogSinkType() { - static const char * const names[2] = { + static const char * const names[3] = { "Console", + "File", nullptr }; return names; } inline const char *EnumNameLogSinkType(LogSinkType e) { - if (::flatbuffers::IsOutRange(e, LogSinkType::Console, LogSinkType::Console)) return ""; + if (::flatbuffers::IsOutRange(e, LogSinkType::Console, LogSinkType::File)) return ""; const size_t index = static_cast(e); return EnumNamesLogSinkType()[index]; } @@ -90,16 +101,14 @@ inline const char *EnumNameLogLevel(LogLevel e) { inline const ::flatbuffers::TypeTable *LogSinkTypeTypeTable() { static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_CHAR, 0, 0 }, { ::flatbuffers::ET_CHAR, 0, 0 } }; static const ::flatbuffers::TypeFunction type_refs[] = { Bigfoot::Flat::LogSinkTypeTypeTable }; - static const char * const names[] = { - "Console" - }; static const ::flatbuffers::TypeTable tt = { - ::flatbuffers::ST_ENUM, 1, type_codes, type_refs, nullptr, nullptr, names + ::flatbuffers::ST_ENUM, 2, type_codes, type_refs, nullptr, nullptr, nullptr }; return &tt; } @@ -116,16 +125,8 @@ inline const ::flatbuffers::TypeTable *LogLevelTypeTable() { static const ::flatbuffers::TypeFunction type_refs[] = { Bigfoot::Flat::LogLevelTypeTable }; - static const char * const names[] = { - "Debug", - "Trace", - "Info", - "Warn", - "Error", - "Critical" - }; static const ::flatbuffers::TypeTable tt = { - ::flatbuffers::ST_ENUM, 6, type_codes, type_refs, nullptr, nullptr, names + ::flatbuffers::ST_ENUM, 6, type_codes, type_refs, nullptr, nullptr, nullptr }; return &tt; } diff --git a/Bigfoot/Sources/System/Include/System/Log/TargetLogger_generated.hpp.in b/Bigfoot/Sources/Utils/Include/Utils/Log/TargetLogger_generated.hpp.in similarity index 93% rename from Bigfoot/Sources/System/Include/System/Log/TargetLogger_generated.hpp.in rename to Bigfoot/Sources/Utils/Include/Utils/Log/TargetLogger_generated.hpp.in index fbfa751..8977a65 100644 --- a/Bigfoot/Sources/System/Include/System/Log/TargetLogger_generated.hpp.in +++ b/Bigfoot/Sources/Utils/Include/Utils/Log/TargetLogger_generated.hpp.in @@ -6,7 +6,7 @@ *********************************************************************/ #ifndef BIGFOOT_@LOGGER_FILENAME_UPPER@_GENERATED_HPP #define BIGFOOT_@LOGGER_FILENAME_UPPER@_GENERATED_HPP -#include +#include #if defined BIGFOOT_NOT_OPTIMIZED diff --git a/Bigfoot/Sources/System/Include/System/Profiler.hpp b/Bigfoot/Sources/Utils/Include/Utils/Profiler.hpp similarity index 96% rename from Bigfoot/Sources/System/Include/System/Profiler.hpp rename to Bigfoot/Sources/Utils/Include/Utils/Profiler.hpp index 0c02d30..2553d62 100644 --- a/Bigfoot/Sources/System/Include/System/Profiler.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Profiler.hpp @@ -4,8 +4,8 @@ * \author Romain BOULLARD * \date October 2025 *********************************************************************/ -#ifndef BIGFOOT_CONCAT_PROFILER_HPP -#define BIGFOOT_CONCAT_PROFILER_HPP +#ifndef BIGFOOT_UTILS_PROFILER_HPP +#define BIGFOOT_UTILS_PROFILER_HPP #ifdef TRACY #include diff --git a/Bigfoot/Sources/Utils/Include/Utils/Singleton.hpp b/Bigfoot/Sources/Utils/Include/Utils/Singleton.hpp index 1d6fa9f..abd933a 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/Singleton.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Singleton.hpp @@ -6,11 +6,7 @@ *********************************************************************/ #ifndef BIGFOOT_UTILS_SINGLETON_HPP #define BIGFOOT_UTILS_SINGLETON_HPP - -#include -#include -#include -#include +#include namespace Bigfoot { @@ -30,9 +26,19 @@ class Singleton * * \return The instance */ - static constexpr TYPE& Instance() + static constexpr TYPE& GetInstance() { - return *eastl::bit_cast(ms_instance.data()); + return ms_instance.value(); + } + + /** + * Is the instance initialized + * + * \return True if initialized, false otherwise + */ + static constexpr bool HasInstance() + { + return ms_instance.has_value(); } class Lifetime @@ -43,11 +49,10 @@ class Singleton * * \param p_args Arguments for the singleton */ - template> || ...)>> + template explicit Lifetime(ARGS&&... p_args) { - Initialize(eastl::forward(p_args)...); + Initialize(std::forward(p_args)...); } Lifetime(const Lifetime& p_lifetime) = delete; @@ -58,15 +63,11 @@ class Singleton Finalize(); } - [[nodiscard]] Lifetime& operator=(const Lifetime& p_lifetime) = delete; - [[nodiscard]] - Lifetime& operator=(Lifetime&& p_lifetime) = delete; + Lifetime& operator=(Lifetime&& p_lifetime) noexcept = delete; }; - [[nodiscard]] Singleton& operator=(const Singleton& p_singleton) = delete; - [[nodiscard]] Singleton& operator=(Singleton&& p_singleton) = delete; private: @@ -78,9 +79,7 @@ class Singleton template static void Initialize(ARGS&&... p_args) { - new (ms_instance.data()) TYPE(eastl::forward(p_args)...); - - ms_initialized = true; + ms_instance.emplace(std::forward(p_args)...); } /** @@ -89,20 +88,13 @@ class Singleton */ static void Finalize() { - eastl::bit_cast(ms_instance.data())->~TYPE(); - - ms_initialized = false; + ms_instance.reset(); } /** * The singleton. */ - alignas(alignof(TYPE)) inline static eastl::array ms_instance; - - /** - * Is the singleton initialized? - */ - inline static bool ms_initialized = false; + inline static eastl::optional ms_instance; }; } // namespace Bigfoot #endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/TaggedType.hpp b/Bigfoot/Sources/Utils/Include/Utils/TaggedType.hpp index a020837..b7cad03 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/TaggedType.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/TaggedType.hpp @@ -4,8 +4,8 @@ * \author Romain BOULLARD * \date December 2025 *********************************************************************/ -#ifndef BIGFOOT_UTILS_TAGGEDTYPE_H -#define BIGFOOT_UTILS_TAGGEDTYPE_H +#ifndef BIGFOOT_UTILS_TAGGEDTYPE_HPP +#define BIGFOOT_UTILS_TAGGEDTYPE_HPP #include @@ -22,14 +22,6 @@ class TaggedType { } - constexpr TaggedType(const TaggedType& p_taggedType) = default; - constexpr TaggedType(TaggedType&& p_taggedType) noexcept = default; - - ~TaggedType() = default; - - constexpr TaggedType& operator=(const TaggedType& p_taggedType) = default; - constexpr TaggedType& operator=(TaggedType&& p_taggedType) noexcept = default; - constexpr TaggedType& operator=(const TYPE& p_value) { m_value = p_value; diff --git a/Bigfoot/Sources/Utils/Include/Utils/TargetMacros.h b/Bigfoot/Sources/Utils/Include/Utils/TargetMacros.h index 3a95305..e067fe2 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/TargetMacros.h +++ b/Bigfoot/Sources/Utils/Include/Utils/TargetMacros.h @@ -7,6 +7,20 @@ #ifndef BIGFOOT_UTILS_TARGETMACROS_H #define BIGFOOT_UTILS_TARGETMACROS_H +#ifdef BIGFOOT_WINDOWS +#define BIGFOOT_WINDOWS_OR_LINUX(p_windows, p_linux) p_windows +#endif + +#ifdef BIGFOOT_LINUX +#define BIGFOOT_WINDOWS_OR_LINUX(p_windows, p_linux) p_linux +#endif + +#ifdef BIGFOOT_WINDOWS +#define BIGFOOT_ALWAYS_INLINE [[msvc::forceinline]] +#elif defined BIGFOOT_LINUX +#define BIGFOOT_ALWAYS_INLINE [[gnu::always_inline]] inline +#endif + #if defined BIGFOOT_OPTIMIZED #define BIGFOOT_OPTIMIZED_ONLY(...) __VA_ARGS__ #define BIGFOOT_NOT_OPTIMIZED_ONLY(...) @@ -18,4 +32,16 @@ #define BIGFOOT_NOT_OPTIMIZED_ONLY(...) __VA_ARGS__ #define BIGFOOT_OPTIMIZED_OR_NOT(p_optimized, p_notOptimized) p_notOptimized #endif + +#ifdef BIGFOOT_TOOLMODE +#define BIGFOOT_TOOLMODE_ONLY(...) __VA_ARGS__ +#define BIGFOOT_GAME_ONLY(...) +#define BIGFOOT_GAME_OR_TOOLMODE(p_game, p_toolmode) p_toolmode +#endif + +#ifdef BIGFOOT_GAME +#define BIGFOOT_TOOLMODE_ONLY(...) +#define BIGFOOT_GAME_ONLY(...) __VA_ARGS__ +#define BIGFOOT_GAME_OR_TOOLMODE(p_game, p_toolmode) p_game +#endif #endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/UtilsAssertHandler.hpp b/Bigfoot/Sources/Utils/Include/Utils/UtilsAssertHandler.hpp new file mode 100644 index 0000000..05825f4 --- /dev/null +++ b/Bigfoot/Sources/Utils/Include/Utils/UtilsAssertHandler.hpp @@ -0,0 +1,19 @@ +/********************************************************************* + * \file UtilsAssertHandler.hpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#ifndef BIGFOOT_UTILS_UTILSASSERTHANDLER_HPP +#define BIGFOOT_UTILS_UTILSASSERTHANDLER_HPP +#include +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +namespace Bigfoot +{ +using UtilsAssertHandler = AssertHandler; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Tests/System/Include/SystemTests/SystemTestsLogger_generated.hpp b/Bigfoot/Sources/Utils/Include/Utils/UtilsLogger_generated.hpp similarity index 52% rename from Bigfoot/Tests/System/Include/SystemTests/SystemTestsLogger_generated.hpp rename to Bigfoot/Sources/Utils/Include/Utils/UtilsLogger_generated.hpp index abe35cd..e58a250 100644 --- a/Bigfoot/Tests/System/Include/SystemTests/SystemTestsLogger_generated.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/UtilsLogger_generated.hpp @@ -1,12 +1,12 @@ // AUTO-GENERATED DO NOT TOUCH /********************************************************************* - * \file SystemTestsLogger.generated.hpp + * \file UtilsLogger.generated.hpp * *********************************************************************/ -#ifndef BIGFOOT_SYSTEMTESTSLOGGER_GENERATED_HPP -#define BIGFOOT_SYSTEMTESTSLOGGER_GENERATED_HPP -#include +#ifndef BIGFOOT_UTILSLOGGER_GENERATED_HPP +#define BIGFOOT_UTILSLOGGER_GENERATED_HPP +#include #if defined BIGFOOT_NOT_OPTIMIZED @@ -15,7 +15,7 @@ namespace Bigfoot /* * Logger */ -inline Log::LoggerInfo SYSTEMTESTS_LOGGER {"SYSTEMTESTS_LOGGER", Flat::LogLevel::Trace}; +inline Log::LoggerInfo UTILS_LOGGER {"UTILS_LOGGER", Flat::LogLevel::Trace}; } // namespace Bigfoot #endif #endif diff --git a/Bigfoot/Sources/Utils/Include/Utils/Version.hpp b/Bigfoot/Sources/Utils/Include/Utils/Version.hpp index 68e0bfa..804c54e 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/Version.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Version.hpp @@ -7,11 +7,13 @@ #ifndef BIGFOOT_UTILS_VERSION_HPP #define BIGFOOT_UTILS_VERSION_HPP #include +#include #include +#include +#include + #include -#include -#include namespace Bigfoot { @@ -42,18 +44,13 @@ class Version { } - Version(const Version& p_version) = default; - Version(Version&& p_version) = default; - - ~Version() = default; - /** * Get the major part of the version. * * \return The major part of the version. */ [[nodiscard]] - constexpr std::uint8_t Major() const + constexpr std::uint8_t GetMajor() const { constexpr std::uint32_t mask = 0b00000000111111110000000000000000; @@ -66,7 +63,7 @@ class Version * \return The minor part of the version. */ [[nodiscard]] - constexpr std::uint8_t Minor() const + constexpr std::uint8_t GetMinor() const { constexpr std::uint32_t mask = 0b00000000000000001111111100000000; @@ -79,54 +76,34 @@ class Version * \return The patch part of the version. */ [[nodiscard]] - constexpr std::uint8_t Patch() const + constexpr std::uint8_t GetPatch() const { constexpr std::uint32_t mask = 0b00000000000000000000000011111111; return static_cast(m_combined & mask); } + /** + * Gets the raw Version + * + * \return the raw version + */ [[nodiscard]] - constexpr operator std::uint32_t() const + constexpr std::uint32_t Raw() const { return m_combined; } - operator std::string() const; - - constexpr Version& operator=(const Version& p_version) = default; - - constexpr Version& operator=(Version&& p_version) = default; + /** + * Gets the string representation of the Version + * + * \return The string + */ + [[nodiscard]] + eastl::string ToString() const; [[nodiscard]] - constexpr bool operator>(const Version& p_version) const - { - return m_combined > p_version.m_combined; - } - - [[nodiscard]] - constexpr bool operator<(const Version& p_version) const - { - return m_combined < p_version.m_combined; - } - - [[nodiscard]] - constexpr bool operator>=(const Version& p_version) const - { - return m_combined >= p_version.m_combined; - } - - [[nodiscard]] - constexpr bool operator<=(const Version& p_version) const - { - return m_combined <= p_version.m_combined; - } - - [[nodiscard]] - constexpr bool operator==(const Version& p_version) const = default; - - [[nodiscard]] - constexpr bool operator!=(const Version& p_version) const = default; + constexpr auto operator<=>(const Version& p_version) const = default; private: /** @@ -138,9 +115,8 @@ class Version /****************************************************************************************/ -#if defined BIGFOOT_NOT_OPTIMIZED template<> -struct std::formatter +struct fmt::formatter { template constexpr auto parse(ParseContext& p_context) @@ -151,8 +127,7 @@ struct std::formatter template auto format(const Bigfoot::Version& p_version, FormatContext& p_context) const { - return std::format_to(p_context.out(), "{}", static_cast(p_version)); + return fmt::format_to(p_context.out(), "{}", p_version.ToString().c_str()); } }; #endif -#endif diff --git a/Bigfoot/Sources/System/Log/Log.cpp b/Bigfoot/Sources/Utils/Log/Log.cpp similarity index 71% rename from Bigfoot/Sources/System/Log/Log.cpp rename to Bigfoot/Sources/Utils/Log/Log.cpp index 4162610..8398a86 100644 --- a/Bigfoot/Sources/System/Log/Log.cpp +++ b/Bigfoot/Sources/Utils/Log/Log.cpp @@ -4,7 +4,7 @@ * \author Romain BOULLARD * \date October 2025 *********************************************************************/ -#include +#include #if defined BIGFOOT_NOT_OPTIMIZED @@ -17,6 +17,12 @@ Log::Log() m_sinks[static_cast(Flat::LogSinkType::Console)] = quill::Frontend::create_or_get_sink( std::string {Flat::EnumNameLogSinkType(Flat::LogSinkType::Console)}); + + quill::FileSinkConfig fileSinkConfig; + fileSinkConfig.set_open_mode('w'); + m_sinks[static_cast(Flat::LogSinkType::File)] = quill::Frontend::create_or_get_sink( + std::string {FILE_SINK_PATH.data()}, + fileSinkConfig); } /****************************************************************************************/ @@ -25,7 +31,8 @@ quill::Logger* Log::RegisterLogger(const LoggerInfo& p_loggerInfo) { quill::Logger* logger = quill::Frontend::create_or_get_logger( p_loggerInfo.m_name, - m_sinks[static_cast(Flat::LogSinkType::Console)]); + {m_sinks[static_cast(Flat::LogSinkType::Console)], + m_sinks[static_cast(Flat::LogSinkType::File)]}); SetLoggerLevel(p_loggerInfo); return logger; @@ -33,6 +40,16 @@ quill::Logger* Log::RegisterLogger(const LoggerInfo& p_loggerInfo) /****************************************************************************************/ +void Log::UnregisterLogger(const LoggerInfo& p_loggerInfo) +{ + if (quill::Logger* logger = quill::Frontend::get_logger(p_loggerInfo.m_name)) + { + quill::Frontend::remove_logger_blocking(logger); + } +} + +/****************************************************************************************/ + quill::Logger* Log::GetLogger(const LoggerInfo& p_loggerInfo) { return quill::Frontend::get_logger(p_loggerInfo.m_name); @@ -79,13 +96,23 @@ void Log::SetLoggerLevel(const LoggerInfo& p_loggerInfo) /****************************************************************************************/ -Log::~Log() +void Log::Flush() { for (quill::Logger* logger: quill::Frontend::get_all_loggers()) { - quill::Frontend::remove_logger(logger); + logger->flush_log(); } +} +/****************************************************************************************/ + +Log::~Log() +{ + Flush(); + for (quill::Logger* logger: quill::Frontend::get_all_loggers()) + { + quill::Frontend::remove_logger_blocking(logger); + } quill::Backend::stop(); } } // namespace Bigfoot diff --git a/Bigfoot/Sources/System/MimallocImpl.cpp b/Bigfoot/Sources/Utils/MimallocImpl.cpp similarity index 99% rename from Bigfoot/Sources/System/MimallocImpl.cpp rename to Bigfoot/Sources/Utils/MimallocImpl.cpp index 35093e8..71fda4e 100644 --- a/Bigfoot/Sources/System/MimallocImpl.cpp +++ b/Bigfoot/Sources/Utils/MimallocImpl.cpp @@ -4,7 +4,7 @@ * \author Romain BOULLARD * \date October 2025 *********************************************************************/ -#include +#include #if defined BIGFOOT_WINDOWS #pragma comment(linker, "/include:mi_version") diff --git a/Bigfoot/Sources/Utils/Version.cpp b/Bigfoot/Sources/Utils/Version.cpp index 678b2fc..465538a 100644 --- a/Bigfoot/Sources/Utils/Version.cpp +++ b/Bigfoot/Sources/Utils/Version.cpp @@ -8,8 +8,8 @@ namespace Bigfoot { -Version::operator std::string() const +eastl::string Version::ToString() const { - return std::format("{}.{}.{}", Major(), Minor(), Patch()); + return Format("{}.{}.{}", GetMajor(), GetMinor(), GetPatch()); } } // namespace Bigfoot diff --git a/Bigfoot/Tests/CMakeLists.txt b/Bigfoot/Tests/CMakeLists.txt index bdd68d7..550d037 100644 --- a/Bigfoot/Tests/CMakeLists.txt +++ b/Bigfoot/Tests/CMakeLists.txt @@ -1,3 +1,8 @@ -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/System) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Misc) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Utils) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine) \ No newline at end of file +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/System) +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/Asset/Asset.cpp b/Bigfoot/Tests/Engine/Asset/Asset.cpp new file mode 100644 index 0000000..d1d6c82 --- /dev/null +++ b/Bigfoot/Tests/Engine/Asset/Asset.cpp @@ -0,0 +1,174 @@ +/********************************************************************* + * \file Asset.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include +#include + +#include +#include + +namespace Bigfoot +{ +class AssetFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + SetActiveAssetLibrary(&m_library); + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuidA = m_assetA.GetHeader().uuid; + const UUID uuidB = m_assetB.GetHeader().uuid; + + m_assetA.GetAsset().ref_a = SoftReference {uuidA}; + + m_assetB.GetAsset().ref_a = HardReference {uuidA}; + m_assetB.GetAsset().refs_a.push_back(SoftReference {uuidA}); + + m_assetC.GetAsset().inner_table->asset_a_ref = HardReference {uuidA}; + m_assetC.GetAsset().inner_table->asset_a_refs.push_back(HardReference {uuidA}); + m_assetC.GetAsset().inner_table->asset_a_refs.push_back(HardReference {uuidA}); + m_assetC.GetAsset().asset_b_ref = HardReference {uuidB}; + m_assetC.GetAsset().asset_b_refs.push_back(SoftReference {uuidB}); + } + + void TearDown() override + { + SetActiveAssetLibrary(&g_assetLibrary); + } + + AssetLibrary m_library; + AssetContainer m_containerA; + AssetContainer m_containerB; + + AssetA m_assetA; + AssetB m_assetB; + AssetC m_assetC; +}; + +/****************************************************************************************/ + +TEST_F(AssetFixture, Constructor_ShouldInitializeHeaderTypeIdAndTypeName) +{ + EXPECT_EQ(m_assetA.GetHeader().type_id, AssetA::GetTypeID()); + EXPECT_EQ(m_assetA.GetHeader().type_name, eastl::string {AssetA::GetTypeName()}); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, SetName_ShouldSetTheName) +{ + m_assetA.SetName("MyAsset"); + + EXPECT_STREQ(m_assetA.GetHeader().name.c_str(), "MyAsset"); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, GetAsset_ShouldReturnAMutableReferenceToTheUnderlyingAsset) +{ + m_assetA.GetAsset().health = 10; + + EXPECT_EQ(m_assetA.GetAsset().health, 10); + EXPECT_EQ(std::as_const(m_assetA).GetAsset().health, 10); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, GetTypeID_ShouldBeStableAcrossCalls) +{ + EXPECT_EQ(AssetA::GetTypeID(), AssetA::GetTypeID()); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, GetTypeID_ShouldBeDifferentForDifferentAssetTypes) +{ + EXPECT_NE(AssetA::GetTypeID(), AssetB::GetTypeID()); + EXPECT_NE(AssetA::GetTypeID(), AssetC::GetTypeID()); + EXPECT_NE(AssetB::GetTypeID(), AssetC::GetTypeID()); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, GetTypeName_ShouldReturnTheFlatbufferFullyQualifiedName) +{ + EXPECT_EQ(AssetA::GetTypeName(), eastl::string_view {AssetA::FLAT_ASSET::GetFullyQualifiedName()}); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, PackThenUnpack_ShouldRoundTripTheHeaderAndTheAsset) +{ + m_assetA.SetName("MyAsset"); + m_assetA.GetAsset().health = 42; + m_assetA.GetAsset().mana = 7; + + AssetA unpacked; + unpacked.Unpack(m_assetA.Pack()); + + EXPECT_EQ(unpacked.GetHeader().uuid, m_assetA.GetHeader().uuid); + EXPECT_STREQ(unpacked.GetHeader().name.c_str(), m_assetA.GetHeader().name.c_str()); + EXPECT_EQ(unpacked.GetHeader().type_id, m_assetA.GetHeader().type_id); + EXPECT_STREQ(unpacked.GetHeader().type_name.c_str(), m_assetA.GetHeader().type_name.c_str()); + EXPECT_EQ(unpacked.GetAsset().health, m_assetA.GetAsset().health); + EXPECT_EQ(unpacked.GetAsset().mana, m_assetA.GetAsset().mana); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, GetHardReferences_ShouldReturnTheHardReferencesOfTheAsset) +{ + const eastl::vector hardReferences = m_assetC.GetHardReferences(m_assetC.Pack()); + + EXPECT_THAT(hardReferences, ::testing::UnorderedElementsAre(m_assetA.GetHeader().uuid, m_assetB.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, CreateAsset_ShouldReturnTheCreatedAsset) +{ + EXPECT_NE(CreateAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, CreateAsset_ShouldCreateUniqueAssets) +{ + const AssetA* first = CreateAsset(); + const AssetA* second = CreateAsset(); + + EXPECT_NE(first->GetHeader().uuid, second->GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, CreateAsset_ShouldReturnTheSameAssetAsItsContainerHandle) +{ + const AssetA* asset = CreateAsset(); + const ContainerHandle* handle = m_containerA.AcquireContainerHandle(asset->GetHeader().uuid); + + EXPECT_EQ(handle->GetAsset(), asset); +} + +/****************************************************************************************/ + +TEST_F(AssetFixture, CreateAsset_ShouldInsertTheAssetIntoItsCorrespondingContainer) +{ + const AssetA* assetA = CreateAsset(); + const AssetB* assetB = CreateAsset(); + + EXPECT_TRUE(m_containerA.Has(assetA->GetHeader().uuid)); + EXPECT_FALSE(m_containerB.Has(assetA->GetHeader().uuid)); + + EXPECT_TRUE(m_containerB.Has(assetB->GetHeader().uuid)); + EXPECT_FALSE(m_containerA.Has(assetB->GetHeader().uuid)); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp new file mode 100644 index 0000000..d63e076 --- /dev/null +++ b/Bigfoot/Tests/Engine/Asset/AssetContainer.cpp @@ -0,0 +1,255 @@ +/********************************************************************* + * \file AssetContainer.cpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#include + +#include + +#include + +namespace Bigfoot +{ +class ContainerHandleFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + m_handle = m_containerA.Insert(); + m_uuid = m_handle->GetAsset()->GetHeader().uuid; + } + + AssetContainer m_containerA; + + UUID m_uuid; + ContainerHandle* m_handle = nullptr; +}; + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, GetAsset_ShouldReturnTheAsset) +{ + EXPECT_NE(m_handle->GetAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, GetAsset_ShouldReturnNullptrIfTheAssetHasBeenRemoved) +{ + m_containerA.Remove(m_uuid); + EXPECT_EQ(m_handle->GetAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, IncrementHardRefCount_ShouldIncrementTheHardRefCount) +{ + const std::uint32_t original = m_handle->GetHardRefCount(); + m_handle->IncrementHardRefCount(); + EXPECT_EQ(original + 1, m_handle->GetHardRefCount()); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, IncrementSoftRefCount_ShouldIncrementTheSoftRefCount) +{ + const std::uint32_t original = m_handle->GetSoftRefCount(); + m_handle->IncrementSoftRefCount(); + EXPECT_EQ(original + 1, m_handle->GetSoftRefCount()); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, DecrementHardRefCount_ShouldDecrementTheHardRefCount) +{ + m_handle->IncrementHardRefCount(); + + const std::uint32_t original = m_handle->GetHardRefCount(); + m_handle->DecrementHardRefCount(); + EXPECT_EQ(original - 1, m_handle->GetHardRefCount()); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, DecrementSoftRefCount_ShouldDecrementTheSoftRefCount) +{ + m_handle->IncrementSoftRefCount(); + + const std::uint32_t original = m_handle->GetSoftRefCount(); + m_handle->DecrementSoftRefCount(); + EXPECT_EQ(original - 1, m_handle->GetSoftRefCount()); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, DecrementHardRefCount_ShouldQueueTheAssetForRemovalWhenItReachesZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, DecrementHardRefCount_ShouldNotQueueTheAssetForRemovalIfItDoesNotReachZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + +TEST_F(ContainerHandleFixture, IncrementHardRefCount_ShouldCancelAPendingRemovalWhenGoingFromZero) +{ + m_handle->IncrementHardRefCount(); + m_handle->DecrementHardRefCount(); + ASSERT_TRUE(m_containerA.IsMarkedForRemove(m_uuid)); + + m_handle->IncrementHardRefCount(); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(m_uuid)); +} + +/****************************************************************************************/ + +class AssetContainerFixture: public ::testing::Test +{ + protected: + AssetContainer m_containerA; +}; + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Has_ShouldReturnTrueIfTheContainerHasTheAsset) +{ + const ContainerHandle* handle = m_containerA.Insert(); + EXPECT_TRUE(m_containerA.Has(handle->GetAsset()->GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Has_ShouldReturnFalseIfTheContainerDoesNotHaveTheAsset) +{ + EXPECT_FALSE(m_containerA.Has(UUID {})); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, AcquireHandle_ShouldReturnTheAssociatedContainerHandle) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + EXPECT_EQ(m_containerA.AcquireContainerHandle(uuid), handle); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, AcquireHandle_ShouldCreateTheAssociatedContainerHandleIfItDoesNotExists) +{ + const ContainerHandle* handle = m_containerA.AcquireContainerHandle(UUID {}); + EXPECT_EQ(handle->GetAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Insert_ShouldReturnAHandleWithTheInsertedAsset) +{ + const ContainerHandle* handle = m_containerA.Insert(); + EXPECT_NE(handle, nullptr); + EXPECT_NE(handle->GetAsset(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, Remove_ShouldRemoveTheAsset) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.Remove(uuid); + + EXPECT_EQ(handle->GetAsset(), nullptr); + EXPECT_FALSE(m_containerA.Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, IsMarkedForRemove_ShouldReturnTrueIfTheAssetIsMarkedForDelete) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, IsMarkedForRemove_ShouldReturnFalseIfTheAssetIsNotMarkedForDelete) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, QueueForRemoval_ShouldMarkTheAssetForRemoval) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + + EXPECT_TRUE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, CancelRemoval_ShouldUnmarkAQueuedAsset) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + m_containerA.CancelRemoval(uuid); + + EXPECT_FALSE(m_containerA.IsMarkedForRemove(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, ProcessPendingRemovals_ShouldRemoveQueuedAssets) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuid); + m_containerA.ProcessPendingRemovals(); + + EXPECT_EQ(handle->GetAsset(), nullptr); + EXPECT_FALSE(m_containerA.Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetContainerFixture, ProcessPendingRemovals_ShouldNotRemoveAssetsThatAreNotQueued) +{ + const ContainerHandle* handle = m_containerA.Insert(); + const UUID uuid = handle->GetAsset()->GetHeader().uuid; + + m_containerA.ProcessPendingRemovals(); + + EXPECT_NE(handle->GetAsset(), nullptr); + EXPECT_TRUE(m_containerA.Has(uuid)); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp b/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp new file mode 100644 index 0000000..53ba2a4 --- /dev/null +++ b/Bigfoot/Tests/Engine/Asset/AssetLibrary.cpp @@ -0,0 +1,261 @@ +/********************************************************************* + * \file AssetLibrary.cpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#include + +#include +#include +#include + +#include + +namespace Bigfoot +{ +class AssetLibraryFixture: public ::testing::Test +{ + protected: + AssetContainer m_containerA; + AssetContainer m_containerB; + AssetContainer m_containerC; + + AssetLibrary m_library; +}; + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, Insert_ShouldReturnTrueOnSuccess) +{ + EXPECT_TRUE(m_library.Insert(&m_containerA)); + EXPECT_TRUE(m_library.Insert(&m_containerB)); + EXPECT_TRUE(m_library.Insert(&m_containerC)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, Insert_ShouldReturnFalseOnDuplicate) +{ + std::ignore = m_library.Insert(&m_containerA); + EXPECT_FALSE(m_library.Insert(&m_containerA)); + + AssetContainer m_containerAA; + EXPECT_FALSE(m_library.Insert(&m_containerAA)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, GetAssetContainer_ShouldReturnTheAssetContainerIfAvailable) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + std::ignore = m_library.Insert(&m_containerC); + + EXPECT_EQ(m_library.GetAssetContainer(GetAssetTypeID()), &m_containerA); + EXPECT_EQ(m_library.GetAssetContainer(GetAssetTypeID()), &m_containerB); + EXPECT_EQ(m_library.GetAssetContainer(GetAssetTypeID()), &m_containerC); + + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(GetAssetTypeID()), &m_containerA); + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(GetAssetTypeID()), &m_containerB); + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(GetAssetTypeID()), &m_containerC); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, GetAssetContainer_ShouldReturnNullptrIfTheContainerDoesNotExist) +{ + std::ignore = m_library.Insert(&m_containerB); + + EXPECT_EQ(m_library.GetAssetContainer(GetAssetTypeID()), nullptr); + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(GetAssetTypeID()), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, GetAssetContainer_ShouldReturnTheContainerThatHasTheAsset) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuid = m_containerB.Insert()->GetAsset()->GetHeader().uuid; + + EXPECT_EQ(m_library.GetAssetContainer(uuid), &m_containerB); + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(uuid), &m_containerB); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, GetAssetContainer_ShouldReturnNullptrIfNoContainerHasTheAsset) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuid; + + EXPECT_EQ(m_library.GetAssetContainer(uuid), nullptr); + EXPECT_EQ(std::as_const(m_library).GetAssetContainer(uuid), nullptr); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, IsLoaded_ShouldReturnTrueIfTheAssetIsLoaded) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuid = m_containerB.Insert()->GetAsset()->GetHeader().uuid; + + EXPECT_TRUE(m_library.IsLoaded(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, IsLoaded_ShouldReturnFalseIfTheAssetIsNotLoaded) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuid; + + EXPECT_FALSE(m_library.IsLoaded(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, IsLoaded_ShouldReturnFalseIfTheAssetWasRemovedFromItsContainer) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuid = m_containerB.Insert()->GetAsset()->GetHeader().uuid; + ASSERT_TRUE(m_library.IsLoaded(uuid)); + + m_containerB.Remove(uuid); + + EXPECT_FALSE(m_library.IsLoaded(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, ProcessPendingRemovals_ShouldProcessRemovalsOnEveryContainer) +{ + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + + const UUID uuidA = m_containerA.Insert()->GetAsset()->GetHeader().uuid; + const UUID uuidB = m_containerB.Insert()->GetAsset()->GetHeader().uuid; + + m_containerA.QueueForRemoval(uuidA); + m_containerB.QueueForRemoval(uuidB); + + m_library.ProcessPendingRemovals(); + + EXPECT_FALSE(m_containerA.Has(uuidA)); + EXPECT_FALSE(m_containerB.Has(uuidB)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, ProcessPendingRemovals_ShouldNotRemoveAssetsThatAreNotQueued) +{ + std::ignore = m_library.Insert(&m_containerA); + + const UUID uuid = m_containerA.Insert()->GetAsset()->GetHeader().uuid; + + m_library.ProcessPendingRemovals(); + + EXPECT_TRUE(m_containerA.Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, ProcessPendingRemovals_ShouldCascadeThroughHardReferencedAssetsOneLevelPerCall) +{ + const ScopedActiveAssetLibrary activeAssetLibraryScoped {&m_library}; + + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + std::ignore = m_library.Insert(&m_containerC); + + UUID leafUUID; + UUID middleUUID; + UUID rootUUID; + + { + // leaf + AssetA* leaf = CreateAsset(); + leafUUID = leaf->GetHeader().uuid; + + // middle --> leaf + AssetB* middle = CreateAsset(); + middle->GetAsset().ref_a = HardReference {leafUUID}; + middleUUID = middle->GetHeader().uuid; + + // root + // / \ + // v v + // middle --> leaf + AssetC* root = CreateAsset(); + root->GetAsset().asset_b_ref = HardReference {middleUUID}; + root->GetAsset().inner_table->asset_a_ref = HardReference {leafUUID}; + rootUUID = root->GetHeader().uuid; + + const HardReference rootRef {rootUUID}; + const HardReference middleRef {middleUUID}; + const HardReference leafRef {leafUUID}; + + ASSERT_NE(rootRef.Get(), nullptr); + ASSERT_NE(middleRef.Get(), nullptr); + ASSERT_NE(leafRef.Get(), nullptr); + } + + // Releasing the outer HardReferences only drops root's count to zero: middle and leaf are + // still kept alive by root's own internal HardReferences to them. + EXPECT_TRUE(m_containerC.IsMarkedForRemove(rootUUID)); + EXPECT_FALSE(m_containerB.IsMarkedForRemove(middleUUID)); + EXPECT_FALSE(m_containerA.IsMarkedForRemove(leafUUID)); + EXPECT_TRUE(m_library.IsLoaded(rootUUID)); + EXPECT_TRUE(m_library.IsLoaded(middleUUID)); + EXPECT_TRUE(m_library.IsLoaded(leafUUID)); + + // 1st pass: removing root destroys its HardReference to middle, dropping middle's count to + // zero; leaf is still held alive by middle's own HardReference to it. + m_library.ProcessPendingRemovals(); + EXPECT_FALSE(m_library.IsLoaded(rootUUID)); + EXPECT_TRUE(m_containerB.IsMarkedForRemove(middleUUID)); + EXPECT_TRUE(m_library.IsLoaded(middleUUID)); + EXPECT_TRUE(m_library.IsLoaded(leafUUID)); + + // 2nd pass: removing middle destroys its HardReference to leaf, dropping leaf's count to zero. + m_library.ProcessPendingRemovals(); + EXPECT_FALSE(m_library.IsLoaded(middleUUID)); + EXPECT_TRUE(m_containerA.IsMarkedForRemove(leafUUID)); + EXPECT_TRUE(m_library.IsLoaded(leafUUID)); + + // 3rd pass: nothing references leaf anymore, it finally gets removed. + m_library.ProcessPendingRemovals(); + EXPECT_FALSE(m_library.IsLoaded(leafUUID)); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, SetActiveAssetLibrary_ShouldSetTheActiveAssetLibraryToTheGivenLibrary) +{ + SetActiveAssetLibrary(&m_library); + EXPECT_EQ(g_activeAssetLibrary, &m_library); + SetActiveAssetLibrary(&g_assetLibrary); +} + +/****************************************************************************************/ + +TEST_F(AssetLibraryFixture, ScopedActiveAssetLibrary_ShouldChangeTheActiveAssetLibraryInAScopedWay) +{ + EXPECT_EQ(g_activeAssetLibrary, &g_assetLibrary); + { + ScopedActiveAssetLibrary activeAssetLibraryScoped {&m_library}; + EXPECT_EQ(g_activeAssetLibrary, &m_library); + } + EXPECT_EQ(g_activeAssetLibrary, &g_assetLibrary); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/Asset/AssetTypeID.cpp b/Bigfoot/Tests/Engine/Asset/AssetTypeID.cpp new file mode 100644 index 0000000..51e9211 --- /dev/null +++ b/Bigfoot/Tests/Engine/Asset/AssetTypeID.cpp @@ -0,0 +1,34 @@ +/********************************************************************* + * \file AssetTypeID.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +TEST(AssetTypeID, Pack_ShouldGiveAFlatAssetTypeIDWithTheSameValue) +{ + constexpr AssetTypeID assetTypeID = 42; + + const ::Flat::Bigfoot::AssetTypeID flatAssetTypeID = flatbuffers::Pack(assetTypeID); + EXPECT_EQ(flatAssetTypeID.value(), assetTypeID); +} + +/****************************************************************************************/ + +TEST(AssetTypeID, UnPack_ShouldGiveBackTheOriginalValue) +{ + constexpr AssetTypeID assetTypeID = 42; + + const ::Flat::Bigfoot::AssetTypeID flatAssetTypeID = flatbuffers::Pack(assetTypeID); + EXPECT_EQ(flatbuffers::UnPack(flatAssetTypeID), assetTypeID); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/Asset/Reference.cpp b/Bigfoot/Tests/Engine/Asset/Reference.cpp new file mode 100644 index 0000000..2732bc0 --- /dev/null +++ b/Bigfoot/Tests/Engine/Asset/Reference.cpp @@ -0,0 +1,295 @@ +/********************************************************************* + * \file Reference.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +template +struct RefCountTraits; + +template<> +struct RefCountTraits> +{ + [[nodiscard]] + static std::uint32_t Get(const ContainerHandle* p_handle) + { + return p_handle->GetHardRefCount(); + } +}; + +template<> +struct RefCountTraits> +{ + [[nodiscard]] + static std::uint32_t Get(const ContainerHandle* p_handle) + { + return p_handle->GetSoftRefCount(); + } +}; + +template +class ReferenceFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + SetActiveAssetLibrary(&m_library); + std::ignore = m_library.Insert(&m_containerA); + } + + void TearDown() override + { + SetActiveAssetLibrary(&g_assetLibrary); + } + + [[nodiscard]] + static std::uint32_t GetRefCount(const ContainerHandle* p_handle) + { + return RefCountTraits::Get(p_handle); + } + + AssetContainer m_containerA; + AssetLibrary m_library; +}; + +class ReferenceTypeNameGenerator +{ + public: + template + static std::string GetName([[maybe_unused]] int p_index) + { + if constexpr (std::is_same_v>) + { + return "HardReference"; + } + if constexpr (std::is_same_v>) + { + return "SoftReference"; + } + } +}; + +using ReferenceTypes = ::testing::Types, SoftReference>; +TYPED_TEST_SUITE(ReferenceFixture, ReferenceTypes, ReferenceTypeNameGenerator); + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, GetUUID_ShouldReturnTheUUID) +{ + UUID uuid; + TypeParam ref {uuid}; + + EXPECT_EQ(ref.GetUUID(), uuid); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, Get_ShouldReturnTheAsset) +{ + const AssetA* inserted = CreateAsset(); + const UUID uuid = inserted->GetHeader().uuid; + + TypeParam ref {uuid}; + + EXPECT_EQ(ref.Get(), inserted); + EXPECT_EQ(std::as_const(ref).Get(), inserted); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, Get_ShouldReturnNullptrIfTheAssetDoesNotExist) +{ + UUID uuid; + TypeParam ref {uuid}; + + EXPECT_EQ(ref.Get(), nullptr); + EXPECT_EQ(std::as_const(ref).Get(), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, Get_ShouldReturnNullptrAfterTheAssetIsRemovedFromTheContainer) +{ + const AssetA* inserted = CreateAsset(); + const UUID uuid = inserted->GetHeader().uuid; + + TypeParam ref {uuid}; + + this->m_containerA.Remove(uuid); + + EXPECT_EQ(ref.Get(), nullptr); + EXPECT_EQ(std::as_const(ref).Get(), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, ShouldTouchTheRefCountOfTheHandle) +{ + UUID uuid; + const ContainerHandle* handle = this->m_containerA.AcquireContainerHandle(uuid); + + EXPECT_EQ(this->GetRefCount(handle), 0); + + { + TypeParam ref1 {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 1); + { + TypeParam ref2 {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 2); + } + EXPECT_EQ(this->GetRefCount(handle), 1); + } + + EXPECT_EQ(this->GetRefCount(handle), 0); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, CopyConstructor_ShouldIncrementTheRefCountAndCopyTheState) +{ + const AssetA* inserted = CreateAsset(); + const UUID uuid = inserted->GetHeader().uuid; + const ContainerHandle* handle = this->m_containerA.AcquireContainerHandle(uuid); + + TypeParam ref1 {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 1); + + TypeParam ref2 {ref1}; + + EXPECT_EQ(this->GetRefCount(handle), 2); + EXPECT_EQ(ref2.GetUUID(), uuid); + EXPECT_EQ(ref2.Get(), inserted); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, MoveConstructor_ShouldTransferTheStateWithoutChangingTheRefCount) +{ + const AssetA* inserted = CreateAsset(); + const UUID uuid = inserted->GetHeader().uuid; + const ContainerHandle* handle = this->m_containerA.AcquireContainerHandle(uuid); + + TypeParam ref1 {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 1); + + TypeParam ref2 {std::move(ref1)}; + + EXPECT_EQ(this->GetRefCount(handle), 1); + EXPECT_EQ(ref2.GetUUID(), uuid); + EXPECT_EQ(ref2.Get(), inserted); + EXPECT_EQ(ref1.GetUUID(), UUID::NULL_UUID); + EXPECT_EQ(ref1.Get(), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, CopyAssignment_ShouldReleaseThePreviousHandleAndAcquireTheNewOne) +{ + UUID uuidA; + UUID uuidB; + const ContainerHandle* handleA = this->m_containerA.AcquireContainerHandle(uuidA); + const ContainerHandle* handleB = this->m_containerA.AcquireContainerHandle(uuidB); + + TypeParam refA {uuidA}; + TypeParam refB {uuidB}; + EXPECT_EQ(this->GetRefCount(handleA), 1); + EXPECT_EQ(this->GetRefCount(handleB), 1); + + refB = refA; + + EXPECT_EQ(this->GetRefCount(handleA), 2); + EXPECT_EQ(this->GetRefCount(handleB), 0); + EXPECT_EQ(refB.GetUUID(), uuidA); + EXPECT_EQ(refB.Get(), refA.Get()); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, CopyAssignment_SelfAssignment_ShouldNotChangeTheRefCount) +{ + UUID uuid; + const ContainerHandle* handle = this->m_containerA.AcquireContainerHandle(uuid); + + TypeParam ref {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 1); + + TypeParam* selfPtr = &ref; + ref = *selfPtr; + + EXPECT_EQ(this->GetRefCount(handle), 1); + EXPECT_EQ(ref.GetUUID(), uuid); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, MoveAssignment_ShouldReleaseThePreviousHandleAndTransferTheNewOne) +{ + UUID uuidA; + UUID uuidB; + const ContainerHandle* handleA = this->m_containerA.AcquireContainerHandle(uuidA); + const ContainerHandle* handleB = this->m_containerA.AcquireContainerHandle(uuidB); + + TypeParam refA {uuidA}; + TypeParam refB {uuidB}; + EXPECT_EQ(this->GetRefCount(handleA), 1); + EXPECT_EQ(this->GetRefCount(handleB), 1); + + refB = std::move(refA); + + EXPECT_EQ(this->GetRefCount(handleA), 1); + EXPECT_EQ(this->GetRefCount(handleB), 0); + EXPECT_EQ(refB.GetUUID(), uuidA); + EXPECT_EQ(refA.GetUUID(), UUID::NULL_UUID); + EXPECT_EQ(refA.Get(), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, MoveAssignment_SelfAssignment_ShouldNotChangeTheRefCount) +{ + UUID uuid; + const ContainerHandle* handle = this->m_containerA.AcquireContainerHandle(uuid); + + TypeParam ref {uuid}; + EXPECT_EQ(this->GetRefCount(handle), 1); + + TypeParam* selfPtr = &ref; + ref = std::move(*selfPtr); + + EXPECT_EQ(this->GetRefCount(handle), 1); + EXPECT_EQ(ref.GetUUID(), uuid); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, EqualityOperator_ShouldReturnTrueForReferencesWithTheSameUUID) +{ + UUID uuid; + TypeParam ref1 {uuid}; + TypeParam ref2 {uuid}; + + EXPECT_EQ(ref1, ref2); +} + +/****************************************************************************************/ + +TYPED_TEST(ReferenceFixture, EqualityOperator_ShouldReturnFalseForReferencesWithDifferentUUIDs) +{ + UUID uuid1; + UUID uuid2; + TypeParam ref1 {uuid1}; + TypeParam ref2 {uuid2}; + + EXPECT_NE(ref1, ref2); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/BigFile/BigFile.cpp b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp new file mode 100644 index 0000000..c23362d --- /dev/null +++ b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp @@ -0,0 +1,563 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_TOOLMODE +class BigFileFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + m_bigFile.Clear(); + } + + /* + * Packs an asset and saves it to the BigFile + * + * \param p_asset The asset to save + * \return The UUID the asset was saved under + */ + template + UUID SaveAsset(ASSET& p_asset) + { + const eastl::vector flatbuffer = p_asset.Pack(); + + m_bigFile.SaveAssetFlatbuffer(p_asset.GetHeader().uuid, flatbuffer, p_asset.GetHardReferences(flatbuffer)); + + return p_asset.GetHeader().uuid; + } + + /* + * Packs and saves several assets to the BigFile in a single SaveAssetFlatbuffers + * + * \param p_assets The assets to save + */ + void SaveAssets(const eastl::vector& p_assets) + { + eastl::vector uuids; + eastl::vector> flatbuffers; + eastl::vector> hardReferences; + + uuids.reserve(p_assets.size()); + flatbuffers.reserve(p_assets.size()); + hardReferences.reserve(p_assets.size()); + + for (AssetBase* const asset: p_assets) + { + eastl::vector flatbuffer = asset->Pack(); + hardReferences.push_back(asset->GetHardReferences(flatbuffer)); + uuids.push_back(asset->GetHeader().uuid); + flatbuffers.push_back(std::move(flatbuffer)); + } + + m_bigFile.SaveAssetFlatbuffers(uuids, flatbuffers, hardReferences); + } + + BigFile m_bigFile {File {BIGFILE_ENGINETESTS_LOCATION}}; +}; + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Clear_ShouldRemoveAllAssetsMetadataAndHardReferences) +{ + AssetA referencedAsset; + referencedAsset.SetName("referenced"); + SaveAsset(referencedAsset); + + AssetB asset; + asset.SetName("hello"); + asset.GetAsset().ref_a = HardReference {referencedAsset.GetHeader().uuid}; + SaveAsset(asset); + + ASSERT_TRUE(m_bigFile.Has(referencedAsset.GetHeader().uuid)); + ASSERT_TRUE(m_bigFile.Has(asset.GetHeader().uuid)); + ASSERT_FALSE(m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid).empty()); + + m_bigFile.Clear(); + + EXPECT_FALSE(m_bigFile.Has(referencedAsset.GetHeader().uuid)); + EXPECT_FALSE(m_bigFile.Has(asset.GetHeader().uuid)); + + EXPECT_TRUE(m_bigFile.GetAssetFlatbuffer(referencedAsset.GetHeader().uuid).empty()); + EXPECT_TRUE(m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid).empty()); + + EXPECT_TRUE(m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid).empty()); +} + +/****************************************************************************************/ + +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; + SaveAsset(asset); + + const eastl::vector savedFlatbuffer = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + EXPECT_EQ(savedFlatbuffer, asset.Pack()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetFlatbuffers_ShouldReturnEmptyForUUIDsThatDoNotExist) +{ + AssetA assetA; + assetA.SetName("hello"); + SaveAsset(assetA); + + AssetA assetB; + assetB.SetName("world"); + SaveAsset(assetB); + + const eastl::array uuids {assetA.GetHeader().uuid, UUID {}, assetB.GetHeader().uuid}; + + const eastl::vector> flatbuffers = m_bigFile.GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 3); + EXPECT_EQ(flatbuffers[0], assetA.Pack()); + EXPECT_TRUE(flatbuffers[1].empty()); + EXPECT_EQ(flatbuffers[2], assetB.Pack()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetFlatbuffers_ShouldReturnEmptyForASingleUUIDThatDoesNotExist) +{ + const eastl::array uuids {UUID {}}; + + const eastl::vector> flatbuffers = m_bigFile.GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 1); + EXPECT_TRUE(flatbuffers[0].empty()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetFlatbuffers_ShouldDuplicateResultsForADuplicateUUID) +{ + AssetA assetA; + assetA.SetName("hello"); + SaveAsset(assetA); + + const eastl::array uuids {assetA.GetHeader().uuid, assetA.GetHeader().uuid}; + + const eastl::vector> flatbuffers = m_bigFile.GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 2); + EXPECT_EQ(flatbuffers[0], assetA.Pack()); + EXPECT_EQ(flatbuffers[1], assetA.Pack()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetHardReference_ShouldReturnEmptyVectorForNonExistentUUID) +{ + const UUID uuid; + + const eastl::vector hardReferences = m_bigFile.GetAssetHardReferences(uuid); + + EXPECT_TRUE(hardReferences.empty()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetHardReference_ShouldGetTheAssetHardReferences) +{ + AssetA referencedAssetX; + referencedAssetX.SetName("referencedX"); + + AssetA referencedAssetY; + referencedAssetY.SetName("referencedY"); + + AssetB asset; + asset.SetName("hello"); + asset.GetAsset().ref_a = HardReference {referencedAssetX.GetHeader().uuid}; + asset.GetAsset().refs_a.push_back(SoftReference {referencedAssetY.GetHeader().uuid}); + SaveAsset(asset); + + const eastl::vector hardReferences = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + + EXPECT_THAT(hardReferences, ::testing::ElementsAre(referencedAssetX.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetHardReferences_ShouldReturnEmptyVectorsForUUIDsThatDoNotExistOrHaveNoReferences) +{ + AssetA referencedAsset; + referencedAsset.SetName("referenced"); + + AssetA assetWithoutReferences; + assetWithoutReferences.SetName("noRefs"); + SaveAsset(assetWithoutReferences); + + AssetB assetWithReferences; + assetWithReferences.SetName("withRefs"); + assetWithReferences.GetAsset().ref_a = HardReference {referencedAsset.GetHeader().uuid}; + SaveAsset(assetWithReferences); + + const UUID missingUUID; + const eastl::array uuids {assetWithoutReferences.GetHeader().uuid, + missingUUID, + assetWithReferences.GetHeader().uuid}; + + const eastl::vector> hardReferences = m_bigFile.GetAssetsHardReferences(uuids); + + ASSERT_EQ(hardReferences.size(), 3); + EXPECT_TRUE(hardReferences[0].empty()); + EXPECT_TRUE(hardReferences[1].empty()); + ASSERT_EQ(hardReferences[2].size(), 1); + EXPECT_EQ(hardReferences[2][0], referencedAsset.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetHardReferences_ShouldDuplicateResultsForADuplicateUUID) +{ + AssetA referencedAssetX; + referencedAssetX.SetName("referencedX"); + + AssetA referencedAssetY; + referencedAssetY.SetName("referencedY"); + + AssetB asset; + asset.SetName("hello"); + asset.GetAsset().ref_a = HardReference {referencedAssetX.GetHeader().uuid}; + asset.GetAsset().refs_a.push_back(SoftReference {referencedAssetY.GetHeader().uuid}); + SaveAsset(asset); + + const eastl::array uuids {asset.GetHeader().uuid, asset.GetHeader().uuid}; + + const eastl::vector> hardReferences = m_bigFile.GetAssetsHardReferences(uuids); + + ASSERT_EQ(hardReferences.size(), 2); + for (const eastl::vector& entry: hardReferences) + { + EXPECT_THAT(entry, ::testing::ElementsAre(referencedAssetX.GetHeader().uuid)); + } +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetTime_ShouldReturnNulloptForNonExistentUUID) +{ + const UUID uuid; + + EXPECT_FALSE(m_bigFile.GetAssetTime(uuid).has_value()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetTime_ShouldReturnCreationAndModificationTimeOnFirstSave) +{ + AssetA asset; + asset.SetName("hello"); + + const std::uint64_t beforeSave = Time::Now().GetEpoch(); + SaveAsset(asset); + + const eastl::optional assetTime = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(assetTime.has_value()); + + constexpr std::uint64_t TOLERANCE_MICROSECONDS = 1'000'000; + const std::uint64_t creationEpoch = assetTime->m_creationTime.GetEpoch(); + const std::uint64_t delta = creationEpoch > beforeSave ? creationEpoch - beforeSave : beforeSave - creationEpoch; + EXPECT_LE(delta, TOLERANCE_MICROSECONDS); + EXPECT_EQ(assetTime->m_creationTime, assetTime->m_modificationTime); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffer_ShouldUpdateModificationTimeButNotCreationTimeOnSecondSave) +{ + AssetA asset; + asset.SetName("hello"); + SaveAsset(asset); + + const eastl::optional firstAssetTime = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(firstAssetTime.has_value()); + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + asset.GetAsset().health = 42; + SaveAsset(asset); + + const eastl::optional secondAssetTime = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(secondAssetTime.has_value()); + + EXPECT_EQ(secondAssetTime->m_creationTime, firstAssetTime->m_creationTime); + EXPECT_GT(secondAssetTime->m_modificationTime.GetEpoch(), firstAssetTime->m_modificationTime.GetEpoch()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffers_ShouldUpdateModificationTimeButNotCreationTimeOnSecondSave) +{ + AssetA asset; + asset.SetName("hello"); + SaveAssets({&asset}); + + const eastl::optional firstAssetTime = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(firstAssetTime.has_value()); + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + asset.GetAsset().health = 42; + SaveAssets({&asset}); + + const eastl::optional secondAssetTime = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(secondAssetTime.has_value()); + + EXPECT_EQ(secondAssetTime->m_creationTime, firstAssetTime->m_creationTime); + EXPECT_GT(secondAssetTime->m_modificationTime.GetEpoch(), firstAssetTime->m_modificationTime.GetEpoch()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetsTime_ShouldReturnNulloptForUUIDsThatDoNotExist) +{ + AssetA asset; + asset.SetName("hello"); + SaveAsset(asset); + + const UUID missingUUID; + const eastl::array uuids {asset.GetHeader().uuid, missingUUID}; + + const eastl::vector> times = m_bigFile.GetAssetsTime(uuids); + + ASSERT_EQ(times.size(), 2); + EXPECT_TRUE(times[0].has_value()); + EXPECT_FALSE(times[1].has_value()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, GetAssetsTime_ShouldDuplicateResultsForADuplicateUUID) +{ + AssetA asset; + asset.SetName("hello"); + SaveAsset(asset); + + const eastl::optional expected = m_bigFile.GetAssetTime(asset.GetHeader().uuid); + ASSERT_TRUE(expected.has_value()); + + const eastl::array uuids {asset.GetHeader().uuid, asset.GetHeader().uuid}; + + const eastl::vector> times = m_bigFile.GetAssetsTime(uuids); + + ASSERT_EQ(times.size(), 2); + ASSERT_TRUE(times[0].has_value()); + ASSERT_TRUE(times[1].has_value()); + EXPECT_EQ(*times[0], *expected); + EXPECT_EQ(*times[1], *expected); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Clear_ShouldRemoveAssetTime) +{ + AssetA asset; + asset.SetName("hello"); + SaveAsset(asset); + + ASSERT_TRUE(m_bigFile.GetAssetTime(asset.GetHeader().uuid).has_value()); + + m_bigFile.Clear(); + + EXPECT_FALSE(m_bigFile.GetAssetTime(asset.GetHeader().uuid).has_value()); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffer_ShouldSaveTheAsset) +{ + AssetA referencedAsset; + referencedAsset.SetName("referenced"); + + AssetB asset; + asset.SetName("hello"); + asset.GetAsset().ref_a = HardReference {referencedAsset.GetHeader().uuid}; + SaveAsset(asset); + + EXPECT_TRUE(m_bigFile.Has(asset.GetHeader().uuid)); + + const eastl::vector hardReferences = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + ASSERT_EQ(hardReferences.size(), 1); + EXPECT_EQ(hardReferences[0], referencedAsset.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffer_ShouldUpdateTheDataWhenCalledTwice) +{ + AssetA referencedAssetX; + referencedAssetX.SetName("referencedX"); + + AssetA referencedAssetY; + referencedAssetY.SetName("referencedY"); + + AssetB asset; + asset.GetAsset().count = 1; + asset.GetAsset().ref_a = HardReference {referencedAssetX.GetHeader().uuid}; + SaveAsset(asset); + + const eastl::vector savedFlatbuffer1 = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + ASSERT_FALSE(savedFlatbuffer1.empty()); + + AssetB savedAsset1; + savedAsset1.Unpack(savedFlatbuffer1); + EXPECT_EQ(savedAsset1.GetAsset().count, 1); + + const eastl::vector hardReferences1 = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + ASSERT_EQ(hardReferences1.size(), 1); + EXPECT_EQ(hardReferences1[0], referencedAssetX.GetHeader().uuid); + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + asset.GetAsset().count = 2; + asset.GetAsset().ref_a = HardReference {referencedAssetY.GetHeader().uuid}; + SaveAsset(asset); + + const eastl::vector savedFlatbuffer2 = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + ASSERT_FALSE(savedFlatbuffer2.empty()); + + AssetB savedAsset2; + savedAsset2.Unpack(savedFlatbuffer2); + EXPECT_EQ(savedAsset2.GetAsset().count, 2); + + const eastl::vector hardReferences2 = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + ASSERT_EQ(hardReferences2.size(), 1); + EXPECT_EQ(hardReferences2[0], referencedAssetY.GetHeader().uuid); + EXPECT_NE(hardReferences2[0], referencedAssetX.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffers_ShouldSaveTheAssets) +{ + AssetA referencedAssetA; + referencedAssetA.SetName("referencedA"); + + AssetA referencedAssetB; + referencedAssetB.SetName("referencedB"); + + AssetB assetA; + assetA.SetName("hello"); + assetA.GetAsset().count = 42; + assetA.GetAsset().ref_a = HardReference {referencedAssetA.GetHeader().uuid}; + + AssetB assetB; + assetB.SetName("world"); + assetB.GetAsset().count = 7; + assetB.GetAsset().ref_a = HardReference {referencedAssetB.GetHeader().uuid}; + + SaveAssets({&assetA, &assetB}); + + EXPECT_TRUE(m_bigFile.Has(assetA.GetHeader().uuid)); + EXPECT_TRUE(m_bigFile.Has(assetB.GetHeader().uuid)); + + const eastl::vector hardReferencesA = m_bigFile.GetAssetHardReferences(assetA.GetHeader().uuid); + ASSERT_EQ(hardReferencesA.size(), 1); + EXPECT_EQ(hardReferencesA[0], referencedAssetA.GetHeader().uuid); + + const eastl::vector hardReferencesB = m_bigFile.GetAssetHardReferences(assetB.GetHeader().uuid); + ASSERT_EQ(hardReferencesB.size(), 1); + EXPECT_EQ(hardReferencesB[0], referencedAssetB.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, SaveAssetFlatbuffers_ShouldUpdateTheDataWhenCalledTwice) +{ + AssetA referencedAssetX; + referencedAssetX.SetName("referencedX"); + + AssetA referencedAssetY; + referencedAssetY.SetName("referencedY"); + + AssetB asset; + asset.GetAsset().count = 1; + asset.GetAsset().ref_a = HardReference {referencedAssetX.GetHeader().uuid}; + SaveAssets({&asset}); + + eastl::vector savedFlatbuffer1 = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + ASSERT_FALSE(savedFlatbuffer1.empty()); + + AssetB savedAsset1; + savedAsset1.Unpack(savedFlatbuffer1); + EXPECT_EQ(savedAsset1.GetAsset().count, 1); + + const eastl::vector hardReferences1 = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + ASSERT_EQ(hardReferences1.size(), 1); + EXPECT_EQ(hardReferences1[0], referencedAssetX.GetHeader().uuid); + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + + asset.GetAsset().count = 2; + asset.GetAsset().ref_a = HardReference {referencedAssetY.GetHeader().uuid}; + SaveAssets({&asset}); + + const eastl::vector savedFlatbuffer2 = m_bigFile.GetAssetFlatbuffer(asset.GetHeader().uuid); + ASSERT_FALSE(savedFlatbuffer2.empty()); + + AssetB savedAsset2; + savedAsset2.Unpack(savedFlatbuffer2); + EXPECT_EQ(savedAsset2.GetAsset().count, 2); + + const eastl::vector hardReferences2 = m_bigFile.GetAssetHardReferences(asset.GetHeader().uuid); + ASSERT_EQ(hardReferences2.size(), 1); + EXPECT_EQ(hardReferences2[0], referencedAssetY.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Has_ShouldReturnTrueIfTheAssetExists) +{ + AssetA asset; + asset.SetName("hello"); + asset.GetAsset().health = 42; + asset.GetAsset().mana = 7; + SaveAsset(asset); + + EXPECT_TRUE(m_bigFile.Has(asset.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Has_ShouldReturnFalseIfTheAssetDoesNotExist) +{ + EXPECT_FALSE(m_bigFile.Has(UUID {})); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/CMakeLists.txt b/Bigfoot/Tests/Engine/CMakeLists.txt index 36652b4..bc71b72 100644 --- a/Bigfoot/Tests/Engine/CMakeLists.txt +++ b/Bigfoot/Tests/Engine/CMakeLists.txt @@ -1,15 +1,15 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}Tests) +set(Dependencies) set(BigfootDependencies - Engine - System - Utils) + AssetsForTesting) + +bigfoot_create_package_tests( + "${Dependencies}" + "${BigfootDependencies}" + "") bigfoot_create_bigfile("Tests/Bigfoot") -bigfoot_create_package_tests( - "" - "${BigfootDependencies}") - -bigfoot_setup_dependencies("Tests/Bigfoot") \ No newline at end of file +bigfoot_create_sasquatch_info() \ No newline at end of file diff --git a/Bigfoot/Tests/Engine/DataAccessor/DataAccessor.cpp b/Bigfoot/Tests/Engine/DataAccessor/DataAccessor.cpp new file mode 100644 index 0000000..0171a40 --- /dev/null +++ b/Bigfoot/Tests/Engine/DataAccessor/DataAccessor.cpp @@ -0,0 +1,51 @@ +/********************************************************************* + * \file DataAccessor.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +namespace Bigfoot +{ +class DataAccessorFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + m_original = g_dataAccessor; + } + + void TearDown() override + { + SetDataAccessor(m_original); + } + + DataAccessorMock m_accessor; + DataAccessor* m_original = nullptr; +}; + +/****************************************************************************************/ + +TEST_F(DataAccessorFixture, SetDataAccessor_ShouldSetTheDataAccessor) +{ + SetDataAccessor(&m_accessor); + EXPECT_EQ(g_dataAccessor, &m_accessor); +} + +/****************************************************************************************/ + +TEST_F(DataAccessorFixture, ScopedDataAccessor_ShouldChangeTheDataAccessorInAScopedWay) +{ + EXPECT_EQ(g_dataAccessor, nullptr); + { + ScopedDataAccessor dataAccessor {&m_accessor}; + EXPECT_EQ(g_dataAccessor, &m_accessor); + } + EXPECT_EQ(g_dataAccessor, nullptr); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/EngineTests/main.cpp b/Bigfoot/Tests/Engine/EngineTests/main.cpp new file mode 100644 index 0000000..0a50313 --- /dev/null +++ b/Bigfoot/Tests/Engine/EngineTests/main.cpp @@ -0,0 +1,21 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#include +#include + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + + GTEST_FLAG_SET(death_test_style, "threadsafe"); + + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime m_lifetime); + + return RUN_ALL_TESTS(); +} diff --git a/Bigfoot/Tests/Engine/Include/EngineTests/BigFileInfo_generated.hpp b/Bigfoot/Tests/Engine/Include/EngineTests/BigFileInfo_generated.hpp index a1f0510..9422ab2 100644 --- a/Bigfoot/Tests/Engine/Include/EngineTests/BigFileInfo_generated.hpp +++ b/Bigfoot/Tests/Engine/Include/EngineTests/BigFileInfo_generated.hpp @@ -9,11 +9,14 @@ #include +#if defined BIGFOOT_TOOLMODE + namespace Bigfoot { /* * BigFile location */ -constexpr eastl::string_view BIGFILE_ENGINETESTS_LOCATION {"./EngineTests-bigfile.db"}; +constexpr eastl::string_view BIGFILE_ENGINETESTS_LOCATION {"./EngineTests.db"}; } // namespace Bigfoot #endif +#endif diff --git a/Bigfoot/Tests/Engine/Include/EngineTests/DataAccessorMock.hpp b/Bigfoot/Tests/Engine/Include/EngineTests/DataAccessorMock.hpp new file mode 100644 index 0000000..260f544 --- /dev/null +++ b/Bigfoot/Tests/Engine/Include/EngineTests/DataAccessorMock.hpp @@ -0,0 +1,44 @@ +/********************************************************************* + * \file DataAccessorMock.hpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#ifndef BIGFOOT_ENGINETESTS_DATAACCESSORMOCK_HPP +#define BIGFOOT_ENGINETESTS_DATAACCESSORMOCK_HPP +#include + +#include + +namespace Bigfoot +{ +class DataAccessorMock: public DataAccessor +{ + public: + MOCK_METHOD(eastl::vector, GetAssetFlatbuffer, (const UUID& p_uuid), (const, override)); + MOCK_METHOD(eastl::vector>, + GetAssetFlatbuffers, + (eastl::span p_uuids), + (const, override)); + MOCK_METHOD(eastl::vector, GetAssetHardReferences, (const UUID& p_uuid), (const, override)); + MOCK_METHOD(eastl::vector>, + GetAssetsHardReferences, + (const eastl::span p_uuids), + (const, override)); + MOCK_METHOD(void, + SaveAssetFlatbuffer, + (const UUID& p_uuid, + const eastl::span p_flatbuffer, + const eastl::span p_hardReferences), + (override)); + MOCK_METHOD(void, + SaveAssetFlatbuffers, + (const eastl::span p_uuids, + const eastl::span> p_flatbuffers, + const eastl::span> p_hardReferences), + (override)); + MOCK_METHOD(bool, Has, (const UUID& p_uuid), (const, override)); +}; +} // namespace Bigfoot + +#endif 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..1f28036 --- /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"}; +} // 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..23d0bb9 --- /dev/null +++ b/Bigfoot/Tests/Engine/Sasquatch/Sasquatch.cpp @@ -0,0 +1,200 @@ +/********************************************************************* + * \file Sasquatch.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include + +#include + +#include +#include + +#include +#include + +namespace Bigfoot +{ +#ifdef BIGFOOT_GAME +class SasquatchFixture: public ::testing::Test +{ + protected: + void SetUp() + { + m_assetA.SetName("hello"); + + m_assetB.SetName("world"); + m_assetB.GetAsset().ref_a = HardReference {m_assetA.GetHeader().uuid}; + + BuildSasquatch({&m_assetA, &m_assetB}); + } + + /* + * Packs the given assets and writes them to a Sasquatch file, then opens it as m_sasquatch + * + * \param p_assets The assets to write, in insertion order + */ + void BuildSasquatch(const eastl::vector& p_assets) + { + struct WriteData + { + UUID m_uuid; + eastl::vector m_flatbuffer; + eastl::vector m_hardReferences; + }; + + eastl::vector writeData; + writeData.reserve(p_assets.size()); + + std::uint64_t totalFlatbufferBytes = 0; + std::size_t totalHardReferencesBytes = 0; + for (AssetBase* const asset: p_assets) + { + eastl::vector flatbuffer = asset->Pack(); + eastl::vector hardReferences = asset->GetHardReferences(flatbuffer); + + totalFlatbufferBytes += flatbuffer.size(); + totalHardReferencesBytes += hardReferences.size() * UUID::UUID_BYTE_SIZE; + + writeData.push_back(WriteData {.m_uuid = asset->GetHeader().uuid, + .m_flatbuffer = std::move(flatbuffer), + .m_hardReferences = std::move(hardReferences)}); + } + + File {SASQUATCH_ENGINETESTS_LOCATION}.Delete(); + { + SasquatchWriter writer {File {SASQUATCH_ENGINETESTS_LOCATION}, + writeData.size(), + totalFlatbufferBytes, + totalHardReferencesBytes}; + for (const WriteData& data: writeData) + { + writer.WriteAsset(data.m_uuid, data.m_flatbuffer, data.m_hardReferences); + } + } + + m_sasquatch.emplace(File {SASQUATCH_ENGINETESTS_LOCATION}); + } + + AssetA m_assetA; + AssetB m_assetB; + + eastl::optional m_sasquatch; +}; + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetHeader_ShouldGetTheHeader) +{ + EXPECT_EQ(m_sasquatch->GetHeader().m_magic, SasquatchHeader::MAGIC); + EXPECT_EQ(m_sasquatch->GetHeader().m_version, SasquatchHeader::VERSION); + EXPECT_EQ(m_sasquatch->GetHeader().m_assetCount, 2); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetHardReference_ShouldReturnEmptyVectorForNonExistentUUID) +{ + const UUID uuid; + + const eastl::vector hardReferences = m_sasquatch->GetAssetHardReferences(uuid); + + EXPECT_TRUE(hardReferences.empty()); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetHardReference_ShouldGetTheAssetHardReferences) +{ + const eastl::vector hardReferences = m_sasquatch->GetAssetHardReferences(m_assetB.GetHeader().uuid); + + EXPECT_THAT(hardReferences, ::testing::ElementsAre(m_assetA.GetHeader().uuid)); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetHardReferences_ShouldReturnEmptyVectorsForUUIDsThatDoNotExistOrHaveNoReferences) +{ + const UUID missingUUID; + const eastl::array uuids {missingUUID, m_assetB.GetHeader().uuid}; + + const eastl::vector> hardReferences = m_sasquatch->GetAssetsHardReferences(uuids); + + ASSERT_EQ(hardReferences.size(), 2); + EXPECT_TRUE(hardReferences[0].empty()); + EXPECT_EQ(hardReferences[1][0], m_assetA.GetHeader().uuid); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetHardReferences_ShouldDuplicateResultsForADuplicateUUID) +{ + const eastl::array uuids {m_assetB.GetHeader().uuid, m_assetB.GetHeader().uuid}; + + const eastl::vector> hardReferences = m_sasquatch->GetAssetsHardReferences(uuids); + + ASSERT_EQ(hardReferences.size(), 2); + for (const eastl::vector& entry: hardReferences) + { + EXPECT_THAT(entry, ::testing::ElementsAre(m_assetA.GetHeader().uuid)); + } +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffer_ShouldReturnEmptyForNonExistentUUID) +{ + EXPECT_TRUE(m_sasquatch->GetAssetFlatbuffer(UUID {}).empty()); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffer_ShouldGetTheAssetFlatbuffer) +{ + const eastl::vector savedFlatbuffer = m_sasquatch->GetAssetFlatbuffer(m_assetA.GetHeader().uuid); + EXPECT_EQ(savedFlatbuffer, m_assetA.Pack()); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffers_ShouldReturnEmptyForNonExistentUUID) +{ + 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(), 3); + EXPECT_EQ(flatbuffers[0], m_assetA.Pack()); + EXPECT_TRUE(flatbuffers[1].empty()); + EXPECT_EQ(flatbuffers[2], m_assetB.Pack()); +} + +/****************************************************************************************/ + +TEST_F(SasquatchFixture, GetAssetFlatbuffers_ShouldDuplicateResultsForADuplicateUUID) +{ + const eastl::vector uuids {m_assetA.GetHeader().uuid, m_assetA.GetHeader().uuid}; + const eastl::vector> flatbuffers = m_sasquatch->GetAssetFlatbuffers(uuids); + + ASSERT_EQ(flatbuffers.size(), 2); + EXPECT_EQ(flatbuffers[0], m_assetA.Pack()); + EXPECT_EQ(flatbuffers[1], m_assetA.Pack()); +} + +/****************************************************************************************/ + +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/Engine/SerializationManager/SerializationManager.cpp b/Bigfoot/Tests/Engine/SerializationManager/SerializationManager.cpp new file mode 100644 index 0000000..570605e --- /dev/null +++ b/Bigfoot/Tests/Engine/SerializationManager/SerializationManager.cpp @@ -0,0 +1,404 @@ +/********************************************************************* + * \file SerializationManager.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#ifdef BIGFOOT_TOOLMODE +#include + +#include +#elif defined BIGFOOT_GAME +#include + +#include +#endif + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace Bigfoot +{ +class SerializationManagerFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + m_previousDataAccessor = g_dataAccessor; + + SetActiveAssetLibrary(&m_library); + std::ignore = m_library.Insert(&m_containerA); + std::ignore = m_library.Insert(&m_containerB); + std::ignore = m_library.Insert(&m_containerC); + + // leaf + AssetA leaf; + leaf.SetName("leaf"); + m_leafUUID = leaf.GetHeader().uuid; + + // middle --> leaf + AssetB middle; + middle.SetName("middle"); + middle.GetAsset().ref_a = HardReference {m_leafUUID}; + m_middleUUID = middle.GetHeader().uuid; + + // root + // / \ + // v v + // middle --> leaf + AssetC root; + root.GetAsset().asset_b_ref = HardReference {m_middleUUID}; + root.GetAsset().inner_table->asset_a_ref = HardReference {m_leafUUID}; + m_rootUUID = root.GetHeader().uuid; + + // cycleX <--> cycleY + AssetB cycleX; + AssetB cycleY; + cycleX.GetAsset().ref_a = HardReference {cycleY.GetHeader().uuid}; + cycleY.GetAsset().ref_a = HardReference {cycleX.GetHeader().uuid}; + m_cycleXUUID = cycleX.GetHeader().uuid; + m_cycleYUUID = cycleY.GetHeader().uuid; + + // sharedChild + AssetA sharedChild; + sharedChild.SetName("shared"); + m_sharedChildUUID = sharedChild.GetHeader().uuid; + + // multiRootA multiRootB + // \ / + // v v + // sharedChild + AssetB multiRootA; + multiRootA.SetName("rootA"); + multiRootA.GetAsset().ref_a = HardReference {m_sharedChildUUID}; + m_multiRootAUUID = multiRootA.GetHeader().uuid; + + AssetB multiRootB; + multiRootB.SetName("rootB"); + multiRootB.GetAsset().ref_a = HardReference {m_sharedChildUUID}; + m_multiRootBUUID = multiRootB.GetHeader().uuid; + + Populate({&leaf, &middle, &root, &cycleX, &cycleY, &sharedChild, &multiRootA, &multiRootB}); + + m_leafFlatbuffer = leaf.Pack(); + m_middleFlatbuffer = middle.Pack(); + m_rootFlatbuffer = root.Pack(); + m_sharedChildFlatbuffer = sharedChild.Pack(); + m_multiRootAFlatbuffer = multiRootA.Pack(); + m_multiRootBFlatbuffer = multiRootB.Pack(); + } + + void TearDown() override + { + SetDataAccessor(m_previousDataAccessor); + SetActiveAssetLibrary(&g_assetLibrary); + } + + /* + * Packs and writes p_assets to the current data accessors (bigfile for tool/sasquatch for game) + * + * \param p_assets The assets to write, in insertion order + */ + void Populate(const eastl::vector& p_assets) + { +#ifdef BIGFOOT_TOOLMODE + m_bigFile.emplace(File {BIGFILE_ENGINETESTS_LOCATION}); + m_bigFile->Clear(); + + for (AssetBase* const asset: p_assets) + { + const eastl::vector flatbuffer = asset->Pack(); + m_bigFile->SaveAssetFlatbuffer(asset->GetHeader().uuid, flatbuffer, asset->GetHardReferences(flatbuffer)); + } + + SetDataAccessor(&*m_bigFile); +#elif defined BIGFOOT_GAME + struct SasquatchData + { + UUID m_uuid; + eastl::vector m_flatbuffer; + eastl::vector m_hardReferences; + }; + + eastl::vector datas; + datas.reserve(p_assets.size()); + + std::size_t totalFlatbufferBytes = 0; + std::size_t totalHardReferencesBytes = 0; + for (AssetBase* const asset: p_assets) + { + eastl::vector flatbuffer = asset->Pack(); + eastl::vector hardReferences = asset->GetHardReferences(flatbuffer); + + totalFlatbufferBytes += flatbuffer.size(); + totalHardReferencesBytes += hardReferences.size() * UUID::UUID_BYTE_SIZE; + + datas.push_back(SasquatchData {.m_uuid = asset->GetHeader().uuid, + .m_flatbuffer = std::move(flatbuffer), + .m_hardReferences = std::move(hardReferences)}); + } + + File {SASQUATCH_ENGINETESTS_LOCATION}.Delete(); + { + SasquatchWriter writer {File {SASQUATCH_ENGINETESTS_LOCATION}, + datas.size(), + totalFlatbufferBytes, + totalHardReferencesBytes}; + for (const SasquatchData& data: datas) + { + writer.WriteAsset(data.m_uuid, data.m_flatbuffer, data.m_hardReferences); + } + } + + m_sasquatch.emplace(File {SASQUATCH_ENGINETESTS_LOCATION}); + SetDataAccessor(&*m_sasquatch); +#endif + } + + DataAccessor* m_previousDataAccessor = nullptr; + + AssetLibrary m_library; + AssetContainer m_containerA; + AssetContainer m_containerB; + AssetContainer m_containerC; + + // An asset with no HardReferences of its own + UUID m_leafUUID; + eastl::vector m_leafFlatbuffer; + + // HardReferences the leaf asset + UUID m_middleUUID; + eastl::vector m_middleFlatbuffer; + + // HardReferences both the middle and leaf assets directly + UUID m_rootUUID; + eastl::vector m_rootFlatbuffer; + + // A pair of assets HardReferencing each other + UUID m_cycleXUUID; + UUID m_cycleYUUID; + + // HardReferenced by both m_multiRootAUUID and m_multiRootBUUID + UUID m_sharedChildUUID; + eastl::vector m_sharedChildFlatbuffer; + + UUID m_multiRootAUUID; + eastl::vector m_multiRootAFlatbuffer; + UUID m_multiRootBUUID; + eastl::vector m_multiRootBFlatbuffer; + +#ifdef BIGFOOT_TOOLMODE + eastl::optional m_bigFile; +#elif defined BIGFOOT_GAME + eastl::optional m_sasquatch; +#endif +}; + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, LoadAsset_ShouldLoadAnAssetWithNoHardReferences) +{ + const HardReference leafRef {m_leafUUID}; + ASSERT_EQ(leafRef.Get(), nullptr); + + SerializationManager::LoadAsset(m_leafUUID); + + ASSERT_NE(leafRef.Get(), nullptr); + EXPECT_EQ(leafRef.Get()->Pack(), m_leafFlatbuffer); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, LoadAsset_ShouldTransitivelyLoadEveryHardReferencedAsset) +{ + const HardReference leafRef {m_leafUUID}; + const HardReference middleRef {m_middleUUID}; + const HardReference rootRef {m_rootUUID}; + + ASSERT_EQ(leafRef.Get(), nullptr); + ASSERT_EQ(middleRef.Get(), nullptr); + ASSERT_EQ(rootRef.Get(), nullptr); + + SerializationManager::LoadAsset(m_rootUUID); + + ASSERT_NE(leafRef.Get(), nullptr); + ASSERT_NE(middleRef.Get(), nullptr); + ASSERT_NE(rootRef.Get(), nullptr); + + EXPECT_EQ(leafRef.Get()->Pack(), m_leafFlatbuffer); + EXPECT_EQ(middleRef.Get()->Pack(), m_middleFlatbuffer); + EXPECT_EQ(rootRef.Get()->Pack(), m_rootFlatbuffer); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, LoadAsset_ShouldSkipAssetsThatAreCaughtInACycle) +{ + const HardReference xRef {m_cycleXUUID}; + const HardReference yRef {m_cycleYUUID}; + + ASSERT_EQ(xRef.Get(), nullptr); + ASSERT_EQ(yRef.Get(), nullptr); + + SerializationManager::LoadAsset(m_cycleXUUID); + + EXPECT_EQ(xRef.Get(), nullptr); + EXPECT_EQ(yRef.Get(), nullptr); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, LoadAsset_ShouldNotReloadAnAssetThatIsAlreadyLoaded) +{ + AssetA* alreadyLoaded = CreateAsset(); + alreadyLoaded->SetName("in-memory"); + const UUID uuid = alreadyLoaded->GetHeader().uuid; + + const HardReference ref {uuid}; + ASSERT_NE(ref.Get(), nullptr); + + SerializationManager::LoadAsset(uuid); + + ASSERT_NE(ref.Get(), nullptr); + EXPECT_STREQ(ref.Get()->GetHeader().name.c_str(), "in-memory"); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, LoadAssets_ShouldLoadEveryRootAndMergeSharedHardReferences) +{ + const HardReference sharedChildRef {m_sharedChildUUID}; + const HardReference rootARef {m_multiRootAUUID}; + const HardReference rootBRef {m_multiRootBUUID}; + + ASSERT_EQ(sharedChildRef.Get(), nullptr); + ASSERT_EQ(rootARef.Get(), nullptr); + ASSERT_EQ(rootBRef.Get(), nullptr); + + const eastl::array roots {m_multiRootAUUID, m_multiRootBUUID}; + SerializationManager::LoadAssets(roots); + + ASSERT_NE(sharedChildRef.Get(), nullptr); + ASSERT_NE(rootARef.Get(), nullptr); + ASSERT_NE(rootBRef.Get(), nullptr); + + EXPECT_EQ(sharedChildRef.Get()->Pack(), m_sharedChildFlatbuffer); + EXPECT_EQ(rootARef.Get()->Pack(), m_multiRootAFlatbuffer); + EXPECT_EQ(rootBRef.Get()->Pack(), m_multiRootBFlatbuffer); +} + +/****************************************************************************************/ + +#ifdef BIGFOOT_TOOLMODE +TEST_F(SerializationManagerFixture, SaveAsset_ShouldSaveTheAsset) +{ + AssetB* asset = CreateAsset(); + const UUID referencedUUID; + asset->GetAsset().ref_a = HardReference {referencedUUID}; + + const UUID uuid = asset->GetHeader().uuid; + + ASSERT_FALSE(g_dataAccessor->Has(uuid)); + + EXPECT_TRUE(SerializationManager::SaveAsset(uuid)); + + ASSERT_TRUE(g_dataAccessor->Has(uuid)); + EXPECT_THAT(g_dataAccessor->GetAssetHardReferences(uuid), ::testing::ElementsAre(referencedUUID)); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, SaveAsset_ShouldFailAndNotSaveIfAnAssetHardReferencesItself) +{ + AssetB* asset = CreateAsset(); + const UUID uuid = asset->GetHeader().uuid; + asset->GetAsset().ref_a = HardReference {uuid}; + + ASSERT_FALSE(g_dataAccessor->Has(uuid)); + + EXPECT_FALSE(SerializationManager::SaveAsset(uuid)); + + EXPECT_FALSE(g_dataAccessor->Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, SaveAssets_ShouldFailAndNotSaveIfTwoAssetsInTheSameSaveHardReferenceEachOther) +{ + AssetB* assetX = CreateAsset(); + AssetB* assetY = CreateAsset(); + + const UUID uuidX = assetX->GetHeader().uuid; + const UUID uuidY = assetY->GetHeader().uuid; + + assetX->GetAsset().ref_a = HardReference {uuidY}; + assetY->GetAsset().ref_a = HardReference {uuidX}; + + ASSERT_FALSE(g_dataAccessor->Has(uuidX)); + ASSERT_FALSE(g_dataAccessor->Has(uuidY)); + + const eastl::array uuids {uuidX, uuidY}; + EXPECT_FALSE(SerializationManager::SaveAssets(uuids)); + + EXPECT_FALSE(g_dataAccessor->Has(uuidX)); + EXPECT_FALSE(g_dataAccessor->Has(uuidY)); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, SaveAssets_ShouldFailAndNotSaveIfAnAlreadySavedHardReferencePointsBackToIt) +{ + AssetB* asset = CreateAsset(); + const UUID uuid = asset->GetHeader().uuid; + const UUID referencedUUID; + asset->GetAsset().ref_a = HardReference {referencedUUID}; + + // referencedUUID is not part of this save; its own already-saved HardReferences (read back from the BigFile) + // point back to uuid, closing a cycle through previously persisted data. Its flatbuffer content is irrelevant. + const AssetA placeholder; + const eastl::array referencedHardReferences {uuid}; + g_dataAccessor->SaveAssetFlatbuffer(referencedUUID, placeholder.Pack(), referencedHardReferences); + + ASSERT_FALSE(g_dataAccessor->Has(uuid)); + + EXPECT_FALSE(SerializationManager::SaveAsset(uuid)); + + EXPECT_FALSE(g_dataAccessor->Has(uuid)); +} + +/****************************************************************************************/ + +TEST_F(SerializationManagerFixture, SaveAssets_ShouldSaveEveryAsset) +{ + const AssetA* assetX = CreateAsset(); + const AssetA* assetY = CreateAsset(); + + const UUID uuidX = assetX->GetHeader().uuid; + const UUID uuidY = assetY->GetHeader().uuid; + + ASSERT_FALSE(g_dataAccessor->Has(uuidX)); + ASSERT_FALSE(g_dataAccessor->Has(uuidY)); + + const eastl::array uuids {uuidX, uuidY}; + EXPECT_TRUE(SerializationManager::SaveAssets(uuids)); + + ASSERT_TRUE(g_dataAccessor->Has(uuidX)); + ASSERT_TRUE(g_dataAccessor->Has(uuidY)); + EXPECT_EQ(g_dataAccessor->GetAssetFlatbuffer(uuidX), assetX->Pack()); + EXPECT_EQ(g_dataAccessor->GetAssetFlatbuffer(uuidY), assetY->Pack()); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/touch.cpp b/Bigfoot/Tests/Engine/touch.cpp deleted file mode 100644 index 38a47cf..0000000 --- a/Bigfoot/Tests/Engine/touch.cpp +++ /dev/null @@ -1 +0,0 @@ -// to delete when an actual test is in EngineTests diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetA.cpp b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetA.cpp new file mode 100644 index 0000000..6c2a0d1 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetA.cpp @@ -0,0 +1,11 @@ +/********************************************************************* + * \file AssetA.cpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#include + +#include + +BIGFOOT_ASSET(AssetA, ::Bigfoot::AssetA) diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetB.cpp b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetB.cpp new file mode 100644 index 0000000..9d14fa7 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetB.cpp @@ -0,0 +1,11 @@ +/********************************************************************* + * \file AssetB.cpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#include + +#include + +BIGFOOT_ASSET(AssetB, ::Bigfoot::AssetB) diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetC.cpp b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetC.cpp new file mode 100644 index 0000000..bb2d42d --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Asset/AssetC.cpp @@ -0,0 +1,11 @@ +/********************************************************************* + * \file AssetC.cpp + * + * \author Romain BOULLARD + * \date July 2026 + *********************************************************************/ +#include + +#include + +BIGFOOT_ASSET(AssetC, ::Bigfoot::AssetC) diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/CMakeLists.txt b/Bigfoot/Tests/Misc/AssetsForTesting/CMakeLists.txt new file mode 100644 index 0000000..c38a828 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/CMakeLists.txt @@ -0,0 +1,15 @@ +get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(${PackageName}) + +set(PublicDependencies) +set(PrivateDependencies) +set(BigfootPublicDependencies) +set(BigfootPrivateDependencies + Engine) + +bigfoot_create_package_tests_helper_lib( + "${PublicDependencies}" + "${PrivateDependencies}" + "${BigfootPublicDependencies}" + "${BigfootPrivateDependencies}" + "") \ No newline at end of file diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.fbs b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.fbs new file mode 100644 index 0000000..b778ca5 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.fbs @@ -0,0 +1,13 @@ +include "Engine/Asset/Reference.fbs"; + +namespace Flat.Bigfoot; + +table AssetA +{ + health: uint = 0; + mana: uint = 0; + + ref_a: SoftReference (native_inline, bigfoot_ref: "::Bigfoot::AssetA"); +} + +root_type AssetA; \ No newline at end of file diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.hpp new file mode 100644 index 0000000..2b0bcd3 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA.hpp @@ -0,0 +1,25 @@ +/********************************************************************* + * \file AssetA.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ASSETSFORTESTING_ASSET_ASSETA_HPP +#define BIGFOOT_ASSETSFORTESTING_ASSET_ASSETA_HPP +#include + +#include + +namespace Bigfoot +{ +struct AssetATraits +{ + using FLAT = ::Flat::Bigfoot::AssetA; +}; + +class AssetA: public Asset +{ +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA_generated.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA_generated.hpp new file mode 100644 index 0000000..b1004e0 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetA_generated.hpp @@ -0,0 +1,251 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_ASSETA_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_ASSETA_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/Reference.hpp" +#include "Engine/Asset/Reference_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct AssetA; +struct AssetABuilder; +struct AssetAT; + +} // namespace Bigfoot +} // namespace Flat + +namespace Bigfoot { +class AssetA; +} // namespace Bigfoot + +namespace flatbuffers { +Flat::Bigfoot::SoftReference PackSoftReferenceAssetA(const ::Bigfoot::SoftReference<::Bigfoot::AssetA>& p_asset); +::Bigfoot::SoftReference<::Bigfoot::AssetA> UnPackSoftReferenceAssetA(const Flat::Bigfoot::SoftReference& p_asset); +} // namespace flatbuffers + +namespace Flat { +namespace Bigfoot { + +inline const ::flatbuffers::TypeTable *AssetATypeTable(); + +struct AssetAT : public ::flatbuffers::NativeTable { + typedef AssetA TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetAT"; + } + uint32_t health = 0; + uint32_t mana = 0; + ::Bigfoot::SoftReference<::Bigfoot::AssetA> ref_a{}; +}; + +struct AssetA FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AssetAT NativeTableType; + typedef AssetABuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetATypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetA"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_HEALTH = 4, + VT_MANA = 6, + VT_REF_A = 8 + }; + uint32_t health() const { + return GetField(VT_HEALTH, 0); + } + uint32_t mana() const { + return GetField(VT_MANA, 0); + } + const Flat::Bigfoot::SoftReference *ref_a() const { + return GetStruct(VT_REF_A); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_HEALTH, 4) && + VerifyField(verifier, VT_MANA, 4) && + VerifyField(verifier, VT_REF_A, 1) && + verifier.EndTable(); + } + AssetAT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(AssetAT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetAT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct AssetABuilder { + typedef AssetA Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_health(uint32_t health) { + fbb_.AddElement(AssetA::VT_HEALTH, health, 0); + } + void add_mana(uint32_t mana) { + fbb_.AddElement(AssetA::VT_MANA, mana, 0); + } + void add_ref_a(const Flat::Bigfoot::SoftReference *ref_a) { + fbb_.AddStruct(AssetA::VT_REF_A, ref_a); + } + explicit AssetABuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAssetA( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t health = 0, + uint32_t mana = 0, + const Flat::Bigfoot::SoftReference *ref_a = nullptr) { + AssetABuilder builder_(_fbb); + builder_.add_ref_a(ref_a); + builder_.add_mana(mana); + builder_.add_health(health); + return builder_.Finish(); +} + +struct AssetA::Traits { + using type = AssetA; + static auto constexpr Create = CreateAssetA; +}; + +::flatbuffers::Offset CreateAssetA(::flatbuffers::FlatBufferBuilder &_fbb, const AssetAT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline AssetAT *AssetA::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void AssetA::UnPackTo(AssetAT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = health(); _o->health = _e; } + { auto _e = mana(); _o->mana = _e; } + { auto _e = ref_a(); if (_e) _o->ref_a = ::flatbuffers::UnPackSoftReferenceAssetA(*_e); } +} + +inline ::flatbuffers::Offset CreateAssetA(::flatbuffers::FlatBufferBuilder &_fbb, const AssetAT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return AssetA::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset AssetA::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetAT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const AssetAT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _health = _o->health; + auto _mana = _o->mana; + auto _ref_a = ::flatbuffers::PackSoftReferenceAssetA(_o->ref_a); + return Flat::Bigfoot::CreateAssetA( + _fbb, + _health, + _mana, + &_ref_a); +} + +inline const ::flatbuffers::TypeTable *AssetATypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_UINT, 0, -1 }, + { ::flatbuffers::ET_UINT, 0, -1 }, + { ::flatbuffers::ET_SEQUENCE, 0, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::SoftReferenceTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 3, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::AssetA *GetAssetA(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::AssetA *GetSizePrefixedAssetA(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyAssetABuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedAssetABuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *AssetAExtension() { + return "bfbs"; +} + +inline void FinishAssetABuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedAssetABuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackAssetA( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetAssetA(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedAssetA( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedAssetA(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +namespace flatbuffers { +#ifndef FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +#define FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +inline Flat::Bigfoot::SoftReference PackSoftReferenceAssetA(const ::Bigfoot::SoftReference<::Bigfoot::AssetA>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::SoftReference<::Bigfoot::AssetA> UnPackSoftReferenceAssetA(const Flat::Bigfoot::SoftReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +} // namespace flatbuffers + +#endif // FLATBUFFERS_GENERATED_ASSETA_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.fbs b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.fbs new file mode 100644 index 0000000..800edf6 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.fbs @@ -0,0 +1,12 @@ +include "Engine/Asset/Reference.fbs"; + +namespace Flat.Bigfoot; + +table AssetB +{ + count: uint = 0; + ref_a: HardReference (native_inline, bigfoot_ref: "::Bigfoot::AssetA"); + refs_a: [SoftReference] (native_inline, bigfoot_ref: "::Bigfoot::AssetA"); +} + +root_type AssetB; \ No newline at end of file diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.hpp new file mode 100644 index 0000000..a411025 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB.hpp @@ -0,0 +1,25 @@ +/********************************************************************* + * \file AssetB.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ASSETSFORTESTING_ASSET_ASSETB_HPP +#define BIGFOOT_ASSETSFORTESTING_ASSET_ASSETB_HPP +#include + +#include + +namespace Bigfoot +{ +struct AssetBTraits +{ + using FLAT = ::Flat::Bigfoot::AssetB; +}; + +class AssetB: public Asset +{ +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB_generated.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB_generated.hpp new file mode 100644 index 0000000..f3fd52e --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetB_generated.hpp @@ -0,0 +1,260 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_ASSETB_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_ASSETB_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/Reference.hpp" +#include "Engine/Asset/Reference_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct AssetB; +struct AssetBBuilder; +struct AssetBT; + +} // namespace Bigfoot +} // namespace Flat + +namespace Bigfoot { +class AssetA; +} // namespace Bigfoot + +namespace flatbuffers { +Flat::Bigfoot::HardReference PackHardReferenceAssetA(const ::Bigfoot::HardReference<::Bigfoot::AssetA>& p_asset); +::Bigfoot::HardReference<::Bigfoot::AssetA> UnPackHardReferenceAssetA(const Flat::Bigfoot::HardReference& p_asset); +Flat::Bigfoot::SoftReference PackSoftReferenceAssetA(const ::Bigfoot::SoftReference<::Bigfoot::AssetA>& p_asset); +::Bigfoot::SoftReference<::Bigfoot::AssetA> UnPackSoftReferenceAssetA(const Flat::Bigfoot::SoftReference& p_asset); +} // namespace flatbuffers + +namespace Flat { +namespace Bigfoot { + +inline const ::flatbuffers::TypeTable *AssetBTypeTable(); + +struct AssetBT : public ::flatbuffers::NativeTable { + typedef AssetB TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetBT"; + } + uint32_t count = 0; + ::Bigfoot::HardReference<::Bigfoot::AssetA> ref_a{}; + eastl::vector<::Bigfoot::SoftReference<::Bigfoot::AssetA>> refs_a{}; +}; + +struct AssetB FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AssetBT NativeTableType; + typedef AssetBBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetBTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetB"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_COUNT = 4, + VT_REF_A = 6, + VT_REFS_A = 8 + }; + uint32_t count() const { + return GetField(VT_COUNT, 0); + } + const Flat::Bigfoot::HardReference *ref_a() const { + return GetStruct(VT_REF_A); + } + const ::flatbuffers::Vector *refs_a() const { + return GetPointer *>(VT_REFS_A); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_COUNT, 4) && + VerifyField(verifier, VT_REF_A, 1) && + VerifyOffset(verifier, VT_REFS_A) && + verifier.VerifyVector(refs_a()) && + verifier.EndTable(); + } + AssetBT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(AssetBT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetBT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct AssetBBuilder { + typedef AssetB Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_count(uint32_t count) { + fbb_.AddElement(AssetB::VT_COUNT, count, 0); + } + void add_ref_a(const Flat::Bigfoot::HardReference *ref_a) { + fbb_.AddStruct(AssetB::VT_REF_A, ref_a); + } + void add_refs_a(::flatbuffers::Offset<::flatbuffers::Vector> refs_a) { + fbb_.AddOffset(AssetB::VT_REFS_A, refs_a); + } + explicit AssetBBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAssetB( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint32_t count = 0, + const Flat::Bigfoot::HardReference *ref_a = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> refs_a = 0) { + AssetBBuilder builder_(_fbb); + builder_.add_refs_a(refs_a); + builder_.add_ref_a(ref_a); + builder_.add_count(count); + return builder_.Finish(); +} + +struct AssetB::Traits { + using type = AssetB; + static auto constexpr Create = CreateAssetB; +}; + +::flatbuffers::Offset CreateAssetB(::flatbuffers::FlatBufferBuilder &_fbb, const AssetBT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline AssetBT *AssetB::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void AssetB::UnPackTo(AssetBT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = count(); _o->count = _e; } + { auto _e = ref_a(); if (_e) _o->ref_a = ::flatbuffers::UnPackHardReferenceAssetA(*_e); } + { auto _e = refs_a(); if (_e) { _o->refs_a.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->refs_a[_i] = ::flatbuffers::UnPackSoftReferenceAssetA(*_e->Get(_i)); } } else { _o->refs_a.resize(0); } } +} + +inline ::flatbuffers::Offset CreateAssetB(::flatbuffers::FlatBufferBuilder &_fbb, const AssetBT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return AssetB::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset AssetB::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetBT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const AssetBT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _count = _o->count; + auto _ref_a = ::flatbuffers::PackHardReferenceAssetA(_o->ref_a); + auto _refs_a = _fbb.CreateVectorOfNativeStructs>(_o->refs_a.data(), _o->refs_a.size(), ::flatbuffers::PackSoftReferenceAssetA); + return Flat::Bigfoot::CreateAssetB( + _fbb, + _count, + &_ref_a, + _refs_a); +} + +inline const ::flatbuffers::TypeTable *AssetBTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_UINT, 0, -1 }, + { ::flatbuffers::ET_SEQUENCE, 0, 0 }, + { ::flatbuffers::ET_SEQUENCE, 1, 1 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::HardReferenceTypeTable, + Flat::Bigfoot::SoftReferenceTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 3, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::AssetB *GetAssetB(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::AssetB *GetSizePrefixedAssetB(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyAssetBBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedAssetBBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *AssetBExtension() { + return "bfbs"; +} + +inline void FinishAssetBBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedAssetBBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackAssetB( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetAssetB(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedAssetB( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedAssetB(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +namespace flatbuffers { +#ifndef FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +#define FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +inline Flat::Bigfoot::HardReference PackHardReferenceAssetA(const ::Bigfoot::HardReference<::Bigfoot::AssetA>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::HardReference<::Bigfoot::AssetA> UnPackHardReferenceAssetA(const Flat::Bigfoot::HardReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +#ifndef FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +#define FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +inline Flat::Bigfoot::SoftReference PackSoftReferenceAssetA(const ::Bigfoot::SoftReference<::Bigfoot::AssetA>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::SoftReference<::Bigfoot::AssetA> UnPackSoftReferenceAssetA(const Flat::Bigfoot::SoftReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETA +} // namespace flatbuffers + +#endif // FLATBUFFERS_GENERATED_ASSETB_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.fbs b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.fbs new file mode 100644 index 0000000..b4d3687 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.fbs @@ -0,0 +1,19 @@ +include "Engine/Asset/Reference.fbs"; + +namespace Flat.Bigfoot; + +table InnerTable +{ + asset_a_ref: HardReference (native_inline, bigfoot_ref: "::Bigfoot::AssetA"); + asset_a_refs: [HardReference] (native_inline, bigfoot_ref: "::Bigfoot::AssetA"); +} + +table AssetC +{ + inner_table: InnerTable (required); + + asset_b_ref: HardReference (native_inline, bigfoot_ref: "::Bigfoot::AssetB"); + asset_b_refs: [SoftReference] (native_inline, bigfoot_ref: "::Bigfoot::AssetB"); +} + +root_type AssetC; \ No newline at end of file diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.hpp new file mode 100644 index 0000000..b9abafc --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC.hpp @@ -0,0 +1,25 @@ +/********************************************************************* + * \file AssetC.hpp + * + * \author Romain BOULLARD + * \date April 2026 + *********************************************************************/ +#ifndef BIGFOOT_ASSETSFORTESTING_ASSET_ASSETC_HPP +#define BIGFOOT_ASSETSFORTESTING_ASSET_ASSETC_HPP +#include + +#include + +namespace Bigfoot +{ +struct AssetCTraits +{ + using FLAT = ::Flat::Bigfoot::AssetC; +}; + +class AssetC: public Asset +{ +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC_generated.hpp b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC_generated.hpp new file mode 100644 index 0000000..47cd534 --- /dev/null +++ b/Bigfoot/Tests/Misc/AssetsForTesting/Include/AssetsForTesting/Asset/AssetC_generated.hpp @@ -0,0 +1,420 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_ASSETC_FLAT_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_ASSETC_FLAT_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "System/UUID/UUID.hpp" +#include "Engine/Asset/Reference.hpp" +#include "Engine/Asset/Reference_generated.hpp" + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Flat { +namespace Bigfoot { + +struct InnerTable; +struct InnerTableBuilder; +struct InnerTableT; + +struct AssetC; +struct AssetCBuilder; +struct AssetCT; + +} // namespace Bigfoot +} // namespace Flat + +namespace Bigfoot { +class AssetA; +} // namespace Bigfoot + +namespace Bigfoot { +class AssetB; +} // namespace Bigfoot + +namespace flatbuffers { +Flat::Bigfoot::HardReference PackHardReferenceAssetA(const ::Bigfoot::HardReference<::Bigfoot::AssetA>& p_asset); +::Bigfoot::HardReference<::Bigfoot::AssetA> UnPackHardReferenceAssetA(const Flat::Bigfoot::HardReference& p_asset); +Flat::Bigfoot::HardReference PackHardReferenceAssetB(const ::Bigfoot::HardReference<::Bigfoot::AssetB>& p_asset); +::Bigfoot::HardReference<::Bigfoot::AssetB> UnPackHardReferenceAssetB(const Flat::Bigfoot::HardReference& p_asset); +Flat::Bigfoot::SoftReference PackSoftReferenceAssetB(const ::Bigfoot::SoftReference<::Bigfoot::AssetB>& p_asset); +::Bigfoot::SoftReference<::Bigfoot::AssetB> UnPackSoftReferenceAssetB(const Flat::Bigfoot::SoftReference& p_asset); +} // namespace flatbuffers + +namespace Flat { +namespace Bigfoot { + +inline const ::flatbuffers::TypeTable *InnerTableTypeTable(); + +inline const ::flatbuffers::TypeTable *AssetCTypeTable(); + +struct InnerTableT : public ::flatbuffers::NativeTable { + typedef InnerTable TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.InnerTableT"; + } + ::Bigfoot::HardReference<::Bigfoot::AssetA> asset_a_ref{}; + eastl::vector<::Bigfoot::HardReference<::Bigfoot::AssetA>> asset_a_refs{}; +}; + +struct InnerTable FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef InnerTableT NativeTableType; + typedef InnerTableBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return InnerTableTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.InnerTable"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_ASSET_A_REF = 4, + VT_ASSET_A_REFS = 6 + }; + const Flat::Bigfoot::HardReference *asset_a_ref() const { + return GetStruct(VT_ASSET_A_REF); + } + const ::flatbuffers::Vector *asset_a_refs() const { + return GetPointer *>(VT_ASSET_A_REFS); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_ASSET_A_REF, 1) && + VerifyOffset(verifier, VT_ASSET_A_REFS) && + verifier.VerifyVector(asset_a_refs()) && + verifier.EndTable(); + } + InnerTableT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(InnerTableT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const InnerTableT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct InnerTableBuilder { + typedef InnerTable Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_asset_a_ref(const Flat::Bigfoot::HardReference *asset_a_ref) { + fbb_.AddStruct(InnerTable::VT_ASSET_A_REF, asset_a_ref); + } + void add_asset_a_refs(::flatbuffers::Offset<::flatbuffers::Vector> asset_a_refs) { + fbb_.AddOffset(InnerTable::VT_ASSET_A_REFS, asset_a_refs); + } + explicit InnerTableBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateInnerTable( + ::flatbuffers::FlatBufferBuilder &_fbb, + const Flat::Bigfoot::HardReference *asset_a_ref = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> asset_a_refs = 0) { + InnerTableBuilder builder_(_fbb); + builder_.add_asset_a_refs(asset_a_refs); + builder_.add_asset_a_ref(asset_a_ref); + return builder_.Finish(); +} + +struct InnerTable::Traits { + using type = InnerTable; + static auto constexpr Create = CreateInnerTable; +}; + +::flatbuffers::Offset CreateInnerTable(::flatbuffers::FlatBufferBuilder &_fbb, const InnerTableT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +struct AssetCT : public ::flatbuffers::NativeTable { + typedef AssetC TableType; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetCT"; + } + eastl::unique_ptr inner_table{new Flat::Bigfoot::InnerTableT()}; + ::Bigfoot::HardReference<::Bigfoot::AssetB> asset_b_ref{}; + eastl::vector<::Bigfoot::SoftReference<::Bigfoot::AssetB>> asset_b_refs{}; + AssetCT() = default; + AssetCT(const AssetCT &o); + AssetCT(AssetCT&&) FLATBUFFERS_NOEXCEPT = default; + AssetCT &operator=(AssetCT o) FLATBUFFERS_NOEXCEPT; +}; + +struct AssetC FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef AssetCT NativeTableType; + typedef AssetCBuilder Builder; + struct Traits; + static const ::flatbuffers::TypeTable *MiniReflectTypeTable() { + return AssetCTypeTable(); + } + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "Flat.Bigfoot.AssetC"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_INNER_TABLE = 4, + VT_ASSET_B_REF = 6, + VT_ASSET_B_REFS = 8 + }; + const Flat::Bigfoot::InnerTable *inner_table() const { + return GetPointer(VT_INNER_TABLE); + } + const Flat::Bigfoot::HardReference *asset_b_ref() const { + return GetStruct(VT_ASSET_B_REF); + } + const ::flatbuffers::Vector *asset_b_refs() const { + return GetPointer *>(VT_ASSET_B_REFS); + } + template + bool Verify(::flatbuffers::VerifierTemplate &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffsetRequired(verifier, VT_INNER_TABLE) && + verifier.VerifyTable(inner_table()) && + VerifyField(verifier, VT_ASSET_B_REF, 1) && + VerifyOffset(verifier, VT_ASSET_B_REFS) && + verifier.VerifyVector(asset_b_refs()) && + verifier.EndTable(); + } + AssetCT *UnPack(const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(AssetCT *_o, const ::flatbuffers::resolver_function_t *_resolver = nullptr) const; + static ::flatbuffers::Offset Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetCT* _o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct AssetCBuilder { + typedef AssetC Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_inner_table(::flatbuffers::Offset inner_table) { + fbb_.AddOffset(AssetC::VT_INNER_TABLE, inner_table); + } + void add_asset_b_ref(const Flat::Bigfoot::HardReference *asset_b_ref) { + fbb_.AddStruct(AssetC::VT_ASSET_B_REF, asset_b_ref); + } + void add_asset_b_refs(::flatbuffers::Offset<::flatbuffers::Vector> asset_b_refs) { + fbb_.AddOffset(AssetC::VT_ASSET_B_REFS, asset_b_refs); + } + explicit AssetCBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + fbb_.Required(o, AssetC::VT_INNER_TABLE); + return o; + } +}; + +inline ::flatbuffers::Offset CreateAssetC( + ::flatbuffers::FlatBufferBuilder &_fbb, + ::flatbuffers::Offset inner_table = 0, + const Flat::Bigfoot::HardReference *asset_b_ref = nullptr, + ::flatbuffers::Offset<::flatbuffers::Vector> asset_b_refs = 0) { + AssetCBuilder builder_(_fbb); + builder_.add_asset_b_refs(asset_b_refs); + builder_.add_asset_b_ref(asset_b_ref); + builder_.add_inner_table(inner_table); + return builder_.Finish(); +} + +struct AssetC::Traits { + using type = AssetC; + static auto constexpr Create = CreateAssetC; +}; + +::flatbuffers::Offset CreateAssetC(::flatbuffers::FlatBufferBuilder &_fbb, const AssetCT *_o, const ::flatbuffers::rehasher_function_t *_rehasher = nullptr); + +inline InnerTableT *InnerTable::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void InnerTable::UnPackTo(InnerTableT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = asset_a_ref(); if (_e) _o->asset_a_ref = ::flatbuffers::UnPackHardReferenceAssetA(*_e); } + { auto _e = asset_a_refs(); if (_e) { _o->asset_a_refs.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->asset_a_refs[_i] = ::flatbuffers::UnPackHardReferenceAssetA(*_e->Get(_i)); } } else { _o->asset_a_refs.resize(0); } } +} + +inline ::flatbuffers::Offset CreateInnerTable(::flatbuffers::FlatBufferBuilder &_fbb, const InnerTableT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return InnerTable::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset InnerTable::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const InnerTableT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const InnerTableT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _asset_a_ref = ::flatbuffers::PackHardReferenceAssetA(_o->asset_a_ref); + auto _asset_a_refs = _fbb.CreateVectorOfNativeStructs>(_o->asset_a_refs.data(), _o->asset_a_refs.size(), ::flatbuffers::PackHardReferenceAssetA); + return Flat::Bigfoot::CreateInnerTable( + _fbb, + &_asset_a_ref, + _asset_a_refs); +} + +inline AssetCT::AssetCT(const AssetCT &o) + : inner_table((o.inner_table) ? new Flat::Bigfoot::InnerTableT(*o.inner_table) : nullptr), + asset_b_ref(o.asset_b_ref), + asset_b_refs(o.asset_b_refs) { +} + +inline AssetCT &AssetCT::operator=(AssetCT o) FLATBUFFERS_NOEXCEPT { + std::swap(inner_table, o.inner_table); + std::swap(asset_b_ref, o.asset_b_ref); + std::swap(asset_b_refs, o.asset_b_refs); + return *this; +} + +inline AssetCT *AssetC::UnPack(const ::flatbuffers::resolver_function_t *_resolver) const { + auto _o = std::make_unique(); + UnPackTo(_o.get(), _resolver); + return _o.release(); +} + +inline void AssetC::UnPackTo(AssetCT *_o, const ::flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; + { auto _e = inner_table(); if (_e) { if(_o->inner_table) { _e->UnPackTo(_o->inner_table.get(), _resolver); } else { _o->inner_table = eastl::unique_ptr(_e->UnPack(_resolver)); } } else if (_o->inner_table) { _o->inner_table.reset(); } } + { auto _e = asset_b_ref(); if (_e) _o->asset_b_ref = ::flatbuffers::UnPackHardReferenceAssetB(*_e); } + { auto _e = asset_b_refs(); if (_e) { _o->asset_b_refs.resize(_e->size()); for (::flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->asset_b_refs[_i] = ::flatbuffers::UnPackSoftReferenceAssetB(*_e->Get(_i)); } } else { _o->asset_b_refs.resize(0); } } +} + +inline ::flatbuffers::Offset CreateAssetC(::flatbuffers::FlatBufferBuilder &_fbb, const AssetCT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) { + return AssetC::Pack(_fbb, _o, _rehasher); +} + +inline ::flatbuffers::Offset AssetC::Pack(::flatbuffers::FlatBufferBuilder &_fbb, const AssetCT* _o, const ::flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { ::flatbuffers::FlatBufferBuilder *__fbb; const AssetCT* __o; const ::flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + auto _inner_table = _o->inner_table ? CreateInnerTable(_fbb, _o->inner_table.get(), _rehasher) : 0; + auto _asset_b_ref = ::flatbuffers::PackHardReferenceAssetB(_o->asset_b_ref); + auto _asset_b_refs = _fbb.CreateVectorOfNativeStructs>(_o->asset_b_refs.data(), _o->asset_b_refs.size(), ::flatbuffers::PackSoftReferenceAssetB); + return Flat::Bigfoot::CreateAssetC( + _fbb, + _inner_table, + &_asset_b_ref, + _asset_b_refs); +} + +inline const ::flatbuffers::TypeTable *InnerTableTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 }, + { ::flatbuffers::ET_SEQUENCE, 1, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::HardReferenceTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 2, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const ::flatbuffers::TypeTable *AssetCTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_SEQUENCE, 0, 0 }, + { ::flatbuffers::ET_SEQUENCE, 0, 1 }, + { ::flatbuffers::ET_SEQUENCE, 1, 2 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Flat::Bigfoot::InnerTableTypeTable, + Flat::Bigfoot::HardReferenceTypeTable, + Flat::Bigfoot::SoftReferenceTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_TABLE, 3, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +inline const Flat::Bigfoot::AssetC *GetAssetC(const void *buf) { + return ::flatbuffers::GetRoot(buf); +} + +inline const Flat::Bigfoot::AssetC *GetSizePrefixedAssetC(const void *buf) { + return ::flatbuffers::GetSizePrefixedRoot(buf); +} + +template +inline bool VerifyAssetCBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifyBuffer(nullptr); +} + +template +inline bool VerifySizePrefixedAssetCBuffer( + ::flatbuffers::VerifierTemplate &verifier) { + return verifier.template VerifySizePrefixedBuffer(nullptr); +} + +inline const char *AssetCExtension() { + return "bfbs"; +} + +inline void FinishAssetCBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.Finish(root); +} + +inline void FinishSizePrefixedAssetCBuffer( + ::flatbuffers::FlatBufferBuilder &fbb, + ::flatbuffers::Offset root) { + fbb.FinishSizePrefixed(root); +} + +inline eastl::unique_ptr UnPackAssetC( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetAssetC(buf)->UnPack(res)); +} + +inline eastl::unique_ptr UnPackSizePrefixedAssetC( + const void *buf, + const ::flatbuffers::resolver_function_t *res = nullptr) { + return eastl::unique_ptr(GetSizePrefixedAssetC(buf)->UnPack(res)); +} + +} // namespace Bigfoot +} // namespace Flat + +namespace flatbuffers { +#ifndef FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +#define FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +inline Flat::Bigfoot::HardReference PackHardReferenceAssetA(const ::Bigfoot::HardReference<::Bigfoot::AssetA>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::HardReference<::Bigfoot::AssetA> UnPackHardReferenceAssetA(const Flat::Bigfoot::HardReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETA +#ifndef FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETB +#define FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETB +inline Flat::Bigfoot::HardReference PackHardReferenceAssetB(const ::Bigfoot::HardReference<::Bigfoot::AssetB>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::HardReference<::Bigfoot::AssetB> UnPackHardReferenceAssetB(const Flat::Bigfoot::HardReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_HARDREFERENCEASSETB +#ifndef FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETB +#define FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETB +inline Flat::Bigfoot::SoftReference PackSoftReferenceAssetB(const ::Bigfoot::SoftReference<::Bigfoot::AssetB>& p_asset) { return {Pack(p_asset.GetUUID())}; } +inline ::Bigfoot::SoftReference<::Bigfoot::AssetB> UnPackSoftReferenceAssetB(const Flat::Bigfoot::SoftReference& p_asset) { return {UnPack(p_asset.uuid())}; } +#endif // FLATBUFFERS_BIGFOOT_REF_SOFTREFERENCEASSETB +} // namespace flatbuffers + +#endif // FLATBUFFERS_GENERATED_ASSETC_FLAT_BIGFOOT_H_ diff --git a/Bigfoot/Tests/Misc/CMakeLists.txt b/Bigfoot/Tests/Misc/CMakeLists.txt new file mode 100644 index 0000000..66cec06 --- /dev/null +++ b/Bigfoot/Tests/Misc/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/AssetsForTesting) \ No newline at end of file diff --git a/Bigfoot/Tests/System/CMakeLists.txt b/Bigfoot/Tests/System/CMakeLists.txt index 7b130e0..07d7c3d 100644 --- a/Bigfoot/Tests/System/CMakeLists.txt +++ b/Bigfoot/Tests/System/CMakeLists.txt @@ -1,14 +1,10 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}Tests) -set(BigfootDependencies - System - Utils) +set(Dependencies) +set(BigfootDependencies) bigfoot_create_package_tests( - "" - "${BigfootDependencies}") - -bigfoot_create_logger() - -bigfoot_setup_dependencies("Tests/Bigfoot") \ No newline at end of file + "${Dependencies}" + "${BigfootDependencies}" + "") \ No newline at end of file diff --git a/Bigfoot/Tests/System/File.cpp b/Bigfoot/Tests/System/File.cpp deleted file mode 100644 index 850afe6..0000000 --- a/Bigfoot/Tests/System/File.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/********************************************************************* - * \file File.cpp - * - * \author Romain BOULLARD - * \date December 2025 - *********************************************************************/ -#include - -#include - -namespace Bigfoot -{ -class FileFixture: public ::testing::Test -{ - public: - File m_file {eastl::string_view {"Fixture/file"}}; - File m_nonExistent {eastl::string_view {"Fixture/bigfoot"}}; -}; - -/****************************************************************************************/ - -TEST_F(FileFixture, IsRelative_ShouldReturnTrueOnRelativeFile) -{ - EXPECT_TRUE(m_file.IsRelative()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, IsRelative_ShouldReturnFalseOnAbsoluteFile) -{ - EXPECT_FALSE(m_file.Absolute().IsRelative()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, IsAbsolute_ShouldReturnTrueOnAbsoluteFile) -{ - EXPECT_TRUE(m_file.Absolute().IsAbsolute()); -} - -TEST_F(FileFixture, IsAbsolute_ShouldReturnFalseOnRelativeFile) -{ - EXPECT_FALSE(m_file.IsAbsolute()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, Exists_ShouldReturnTrueOnExistingFile) -{ - EXPECT_TRUE(m_file.Exists()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, Exists_ShouldReturnFalseOnNonExistingFile) -{ - EXPECT_FALSE(m_nonExistent.Exists()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, Path_ShouldReturnThePath) -{ - EXPECT_STREQ(m_file.Path().data(), "Fixture/file"); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, Absolute_ShouldReturnTheAbsolutePath) -{ - EXPECT_STREQ(std::filesystem::absolute("Fixture/file").string().c_str(), m_file.Absolute().Path().data()); -} - -/****************************************************************************************/ - -TEST_F(FileFixture, Relative_ShouldReturnTheRelativePath) -{ - EXPECT_STREQ(std::filesystem::relative("Fixture/file").string().c_str(), m_file.Relative().Path().data()); -} -} // namespace Bigfoot diff --git a/Bigfoot/Tests/System/File/File.cpp b/Bigfoot/Tests/System/File/File.cpp new file mode 100644 index 0000000..97430ac --- /dev/null +++ b/Bigfoot/Tests/System/File/File.cpp @@ -0,0 +1,196 @@ +/********************************************************************* + * \file File.cpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#include + +#include + +#include +#include +#include + +namespace Bigfoot +{ +class FileFixture: public ::testing::Test +{ + public: + File m_file {eastl::string_view {"Fixture/file"}}; + File m_nonExistent {eastl::string_view {"Fixture/bigfoot"}}; + File m_writeScratchFile {eastl::string_view {"Fixture/scratch"}}; + + protected: + void SetUp() override + { + m_writeScratchFile.Delete(); + } +}; + +/****************************************************************************************/ + +TEST_F(FileFixture, IsRelative_ShouldReturnTrueOnRelativeFile) +{ + EXPECT_TRUE(m_file.IsRelative()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, IsRelative_ShouldReturnFalseOnAbsoluteFile) +{ + EXPECT_FALSE(m_file.ToAbsolute().IsRelative()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, IsAbsolute_ShouldReturnTrueOnAbsoluteFile) +{ + EXPECT_TRUE(m_file.ToAbsolute().IsAbsolute()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, IsAbsolute_ShouldReturnFalseOnRelativeFile) +{ + EXPECT_FALSE(m_file.IsAbsolute()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Exists_ShouldReturnTrueOnExistingFile) +{ + EXPECT_TRUE(m_file.Exists()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Exists_ShouldReturnFalseOnNonExistingFile) +{ + EXPECT_FALSE(m_nonExistent.Exists()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, GetPath_ShouldReturnThePath) +{ + EXPECT_STREQ(m_file.GetPath().data(), "Fixture/file"); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, ToAbsolute_ShouldReturnTheAbsolutePath) +{ + EXPECT_STREQ(std::filesystem::absolute("Fixture/file").string().c_str(), m_file.ToAbsolute().GetPath().data()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, ToRelative_ShouldReturnTheRelativePath) +{ + EXPECT_STREQ(std::filesystem::relative("Fixture/file").string().c_str(), m_file.ToRelative().GetPath().data()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, GetSize_ShouldReturnZeroForAnEmptyFile) +{ + EXPECT_EQ(m_file.GetSize(), 0); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, GetSize_ShouldReturnTheFileSize) +{ + constexpr eastl::string_view content = "Hello, Sasquatch!"; + m_writeScratchFile.Write({std::bit_cast(content.data()), content.size()}, 0); + + EXPECT_EQ(m_writeScratchFile.GetSize(), content.size()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Read_ShouldReturnTheRequestedBytesAtTheGivenOffset) +{ + constexpr eastl::string_view content = "Hello, Sasquatch!"; + m_writeScratchFile.Write({std::bit_cast(content.data()), content.size()}, 0); + + const eastl::vector data = m_writeScratchFile.Read(7, 10); + ASSERT_EQ(data.size(), 10); + EXPECT_EQ(std::memcmp(data.data(), "Sasquatch!", data.size()), 0); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Write_ShouldCreateTheFileWithTheGivenContent) +{ + constexpr eastl::string_view content = "Hello, Sasquatch!"; + m_writeScratchFile.Write({std::bit_cast(content.data()), content.size()}, 0); + + const eastl::vector data = m_writeScratchFile.Read(0, m_writeScratchFile.GetSize()); + ASSERT_EQ(data.size(), content.size()); + EXPECT_EQ(std::memcmp(data.data(), content.data(), data.size()), 0); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Write_ShouldNotTouchContentOutsideTheWrittenRange) +{ + constexpr eastl::string_view initial = "Hello, Sasquatch!"; + m_writeScratchFile.Write({std::bit_cast(initial.data()), initial.size()}, 0); + + constexpr eastl::string_view patch = "BIGFOOT"; + m_writeScratchFile.Write({std::bit_cast(patch.data()), patch.size()}, 7); + + const eastl::vector data = m_writeScratchFile.Read(0, m_writeScratchFile.GetSize()); + ASSERT_EQ(data.size(), initial.size()); + EXPECT_EQ(std::memcmp(data.data(), "Hello, ", 7), 0); + EXPECT_EQ(std::memcmp(data.data() + 7, "BIGFOOT", 7), 0); + EXPECT_EQ(std::memcmp(data.data() + 14, "ch!", 3), 0); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Write_ShouldRoundTripArbitraryData) +{ + struct Payload + { + int m_intValue; + double m_doubleValue; + std::array m_tag; + + bool operator==(const Payload&) const = default; + }; + + static_assert(std::is_trivially_copyable_v); + + constexpr Payload payload {.m_intValue = 42, .m_doubleValue = 3.14, .m_tag = {'B', 'I', 'G', 'F'}}; + m_writeScratchFile.Write({std::bit_cast(&payload), sizeof(payload)}, 0); + + const eastl::vector data = m_writeScratchFile.Read(0, m_writeScratchFile.GetSize()); + ASSERT_EQ(data.size(), sizeof(Payload)); + EXPECT_EQ(*std::bit_cast(data.data()), payload); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Delete_ShouldRemoveTheFile) +{ + constexpr std::byte payload {1}; + + m_writeScratchFile.Write({&payload, 1}, 0); + ASSERT_TRUE(m_writeScratchFile.Exists()); + + m_writeScratchFile.Delete(); + EXPECT_FALSE(m_writeScratchFile.Exists()); +} + +/****************************************************************************************/ + +TEST_F(FileFixture, Delete_ShouldDoNothingWhenTheFileDoesNotExist) +{ + ASSERT_FALSE(m_writeScratchFile.Exists()); + m_writeScratchFile.Delete(); + EXPECT_FALSE(m_writeScratchFile.Exists()); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/System/File/MemoryMappedFile.cpp b/Bigfoot/Tests/System/File/MemoryMappedFile.cpp new file mode 100644 index 0000000..ac80082 --- /dev/null +++ b/Bigfoot/Tests/System/File/MemoryMappedFile.cpp @@ -0,0 +1,92 @@ +/********************************************************************* + * \file MemoryMappedFile.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include +#include + +namespace Bigfoot +{ +class MemoryMappedFileFixture: public ::testing::Test +{ + protected: + MemoryMappedFile m_file {File {eastl::string_view {"Fixture/mappedFile"}}}; + MemoryMappedFile m_emptyFile {File {eastl::string_view {"Fixture/file"}}}; +}; + +/****************************************************************************************/ + +TEST_F(MemoryMappedFileFixture, GetData_ShouldReturnTheFileContent) +{ + const eastl::span data = m_file.GetData(); + ASSERT_EQ(data.size(), std::strlen("Hello, Sasquatch!")); + EXPECT_EQ(std::memcmp(data.data(), "Hello, Sasquatch!", data.size()), 0); +} + +/****************************************************************************************/ + +TEST_F(MemoryMappedFileFixture, GetData_ShouldReturnAnEmptySpanForAnEmptyFile) +{ + EXPECT_TRUE(m_emptyFile.GetData().empty()); +} + +/****************************************************************************************/ + +class WritableMemoryMappedFileFixture: public ::testing::Test +{ + protected: + static constexpr eastl::string_view LOCATION = "Fixture/WritableMemoryMappedFileTests-scratch"; + + void SetUp() override + { + File file {LOCATION}; + file.Delete(); + } +}; + +/****************************************************************************************/ + +TEST_F(WritableMemoryMappedFileFixture, GetData_ShouldReturnASpanOfTheRequestedSize) +{ + WritableMemoryMappedFile file {File {LOCATION}, 17}; + EXPECT_EQ(file.GetData().size(), 17); +} + +/****************************************************************************************/ + +TEST_F(WritableMemoryMappedFileFixture, Write_ShouldWriteToTheFile) +{ + { + WritableMemoryMappedFile file {File {LOCATION}, std::strlen("Hello, Sasquatch!")}; + std::memcpy(file.GetData().data(), "Hello, Sasquatch!", file.GetData().size()); + } + + const MemoryMappedFile readBack {File {LOCATION}}; + ASSERT_EQ(readBack.GetData().size(), std::strlen("Hello, Sasquatch!")); + EXPECT_EQ(std::memcmp(readBack.GetData().data(), "Hello, Sasquatch!", readBack.GetData().size()), 0); +} + +/****************************************************************************************/ + +TEST_F(WritableMemoryMappedFileFixture, Constructor_ShouldNotTruncateAnyExistingContent) +{ + { + WritableMemoryMappedFile file {File {LOCATION}, 100}; + std::memset(file.GetData().data(), 0xAB, file.GetData().size()); + } + + const WritableMemoryMappedFile file {File {LOCATION}, 4}; + constexpr std::array reference {std::byte {0xAB}, + std::byte {0xAB}, + std::byte {0xAB}, + std::byte {0xAB}}; + EXPECT_EQ(std::memcmp(file.GetData().data(), reference.data(), reference.size()), 0); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/System/Fixture/mappedFile b/Bigfoot/Tests/System/Fixture/mappedFile new file mode 100644 index 0000000..b8efbe4 --- /dev/null +++ b/Bigfoot/Tests/System/Fixture/mappedFile @@ -0,0 +1 @@ +Hello, Sasquatch! \ No newline at end of file diff --git a/Bigfoot/Tests/System/Log/Log.cpp b/Bigfoot/Tests/System/Log/Log.cpp deleted file mode 100644 index 49a2e21..0000000 --- a/Bigfoot/Tests/System/Log/Log.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/********************************************************************* - * \file Log.cpp - * - * \author Romain BOULLARD - * \date December 2022 - *********************************************************************/ -#include - -#include - -#include - -#if defined BIGFOOT_NOT_OPTIMIZED - -#include - -namespace Bigfoot -{ -class LogFixture: public ::testing::Test -{ - protected: - void SetUp() override - { - SYSTEMTESTS_LOGGER = {"UTILSTESTS_LOGGER", Flat::LogLevel::Trace}; - } - - static constexpr Flat::LogLevel QuillLogLevelToLogLevel(const quill::LogLevel p_level) - { - switch (p_level) - { - case quill::LogLevel::Debug: - return Flat::LogLevel::Debug; - case quill::LogLevel::TraceL3: - return Flat::LogLevel::Trace; - case quill::LogLevel::Info: - return Flat::LogLevel::Info; - case quill::LogLevel::Warning: - return Flat::LogLevel::Warn; - case quill::LogLevel::Error: - return Flat::LogLevel::Error; - case quill::LogLevel::Critical: - return Flat::LogLevel::Critical; - default: - break; - } - - return Flat::LogLevel::Trace; - } - - Log m_log; -}; - -/****************************************************************************************/ - -TEST_F(LogFixture, RegisterLogger_ShouldRegisterTheLogger) -{ - const quill::Logger* logger = m_log.RegisterLogger(SYSTEMTESTS_LOGGER); - EXPECT_TRUE(logger); - EXPECT_EQ(logger, m_log.GetLogger(SYSTEMTESTS_LOGGER)); - EXPECT_EQ(logger->get_logger_name(), SYSTEMTESTS_LOGGER.m_name); - EXPECT_EQ(QuillLogLevelToLogLevel(logger->get_log_level()), SYSTEMTESTS_LOGGER.m_level); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, GetLogger_ShouldReturnNullptrIfTheLoggerDoesNotExist) -{ - EXPECT_FALSE(m_log.GetLogger(SYSTEMTESTS_LOGGER)); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, GetLogger_ShouldReturnTheLoggerIfItExists) -{ - [[maybe_unused]] - const quill::Logger* logger = m_log.RegisterLogger(SYSTEMTESTS_LOGGER); - EXPECT_TRUE(m_log.GetLogger(SYSTEMTESTS_LOGGER)); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, ChangeLoggerLogLevel_ShouldChangeTheLoggerLogLevel) -{ - const quill::Logger* logger = m_log.RegisterLogger(SYSTEMTESTS_LOGGER); - - m_log.ChangeLoggerLogLevel(SYSTEMTESTS_LOGGER, Flat::LogLevel::Critical); - EXPECT_EQ(QuillLogLevelToLogLevel(logger->get_log_level()), Flat::LogLevel::Critical); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogDebug) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_DEBUG(SYSTEMTESTS_LOGGER, "Hello"); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogTrace) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_TRACE(SYSTEMTESTS_LOGGER, "Hello"); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogInfo) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_INFO(SYSTEMTESTS_LOGGER, "Hello"); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogWarn) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_WARN(SYSTEMTESTS_LOGGER, "Hello"); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogError) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_ERROR(SYSTEMTESTS_LOGGER, "Hello"); -} - -/****************************************************************************************/ - -TEST_F(LogFixture, LogFatal) -{ - Singleton::Lifetime singletonLifetime; - BIGFOOT_LOG_FATAL(SYSTEMTESTS_LOGGER, "Hello"); -} -} // namespace Bigfoot -#endif diff --git a/Bigfoot/Tests/System/SystemTests/main.cpp b/Bigfoot/Tests/System/SystemTests/main.cpp new file mode 100644 index 0000000..0a50313 --- /dev/null +++ b/Bigfoot/Tests/System/SystemTests/main.cpp @@ -0,0 +1,21 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#include +#include + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + + GTEST_FLAG_SET(death_test_style, "threadsafe"); + + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime m_lifetime); + + return RUN_ALL_TESTS(); +} diff --git a/Bigfoot/Tests/System/Time.cpp b/Bigfoot/Tests/System/Time.cpp deleted file mode 100644 index 6a9b602..0000000 --- a/Bigfoot/Tests/System/Time.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/********************************************************************* - * \file Time.cpp - * - * \author Romain BOULLARD - * \date December 2025 - *********************************************************************/ -#include - -#include - -namespace Bigfoot -{ -class TimeFixture: public ::testing::Test -{ - public: - Time m_time {1'767'643'746'680'609}; -}; - -/****************************************************************************************/ - -TEST_F(TimeFixture, Year_ShouldReturnTheYear) -{ - EXPECT_EQ(m_time.Year(), 2026); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Month_ShouldReturnTheMonth) -{ - EXPECT_EQ(m_time.Month(), 1); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Day_ShouldReturnTheDay) -{ - EXPECT_EQ(m_time.Day(), 5); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Hour_ShouldReturnTheHour) -{ - EXPECT_EQ(m_time.Hour(), 20); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Minute_ShouldReturnTheMinute) -{ - EXPECT_EQ(m_time.Minute(), 9); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Second_ShouldReturnTheSecond) -{ - EXPECT_EQ(m_time.Second(), 6); -} - -/****************************************************************************************/ - -TEST_F(TimeFixture, Microsecond_ShouldReturnTheMicrosecond) -{ - EXPECT_EQ(m_time.Microsecond(), 680'609); -} -} // namespace Bigfoot diff --git a/Bigfoot/Tests/System/Time/Time.cpp b/Bigfoot/Tests/System/Time/Time.cpp new file mode 100644 index 0000000..a03d462 --- /dev/null +++ b/Bigfoot/Tests/System/Time/Time.cpp @@ -0,0 +1,85 @@ +/********************************************************************* + * \file Time.cpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#include + +#include + +#include + +namespace Bigfoot +{ +class TimeFixture: public ::testing::Test +{ + public: + Time m_time {1'767'643'746'680'609}; +}; + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetYear_ShouldReturnTheYear) +{ + EXPECT_EQ(m_time.GetYear(), 2026); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetMonth_ShouldReturnTheMonth) +{ + EXPECT_EQ(m_time.GetMonth(), 1); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetDay_ShouldReturnTheDay) +{ + EXPECT_EQ(m_time.GetDay(), 5); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetHour_ShouldReturnTheHour) +{ + EXPECT_EQ(m_time.GetHour(), 20); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetMinute_ShouldReturnTheMinute) +{ + EXPECT_EQ(m_time.GetMinute(), 9); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetSecond_ShouldReturnTheSecond) +{ + EXPECT_EQ(m_time.GetSecond(), 6); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, GetMicrosecond_ShouldReturnTheMicrosecond) +{ + EXPECT_EQ(m_time.GetMicrosecond(), 680'609); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, Pack_ShouldGiveAFlatTimeWithTheSameEpoch) +{ + const ::Flat::Bigfoot::Time flatTime = flatbuffers::Pack(m_time); + EXPECT_EQ(flatTime.epoch(), m_time.GetEpoch()); +} + +/****************************************************************************************/ + +TEST_F(TimeFixture, UnPack_ShouldGiveBackTheOriginalTime) +{ + const ::Flat::Bigfoot::Time flatTime = flatbuffers::Pack(m_time); + EXPECT_EQ(flatbuffers::UnPack(flatTime), m_time); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/System/UUID/UUID.cpp b/Bigfoot/Tests/System/UUID/UUID.cpp index 8f978d9..6d116e8 100644 --- a/Bigfoot/Tests/System/UUID/UUID.cpp +++ b/Bigfoot/Tests/System/UUID/UUID.cpp @@ -6,8 +6,12 @@ *********************************************************************/ #include -#include +#include +#include + +#include +#include #include namespace Bigfoot @@ -44,13 +48,13 @@ class UUIDFixture: public ::testing::Test TEST_F(UUIDFixture, DumpingRawAndConstructAnUUIDWithItShouldGiveTheSameUUID) { - std::span raw = m_a; + std::span raw = m_a.ToBytes(); EXPECT_EQ(m_a, UUID {raw}); - raw = m_b; + raw = m_b.ToBytes(); EXPECT_EQ(m_b, UUID {raw}); - raw = m_d; + raw = m_d.ToBytes(); EXPECT_EQ(m_d, UUID {raw}); } @@ -58,21 +62,21 @@ TEST_F(UUIDFixture, DumpingRawAndConstructAnUUIDWithItShouldGiveTheSameUUID) TEST_F(UUIDFixture, ValidIDFromStringShouldBeEqualToString) { - EXPECT_EQ(static_cast(m_b), "47183823-2574-4bfd-b411-99ed177d3e43"); + EXPECT_EQ(m_b.ToString(), "47183823-2574-4bfd-b411-99ed177d3e43"); } /****************************************************************************************/ TEST_F(UUIDFixture, InvalidIDFromStringShouldNotBeEqualToString) { - EXPECT_NE(static_cast(m_c), "4bfd-b411-99ed177d3e43"); + EXPECT_NE(m_c.ToString(), "4bfd-b411-99ed177d3e43"); } /****************************************************************************************/ TEST_F(UUIDFixture, NullIDStringShouldBeEqualToNullIDString) { - EXPECT_EQ(static_cast(UUID::NULL_UUID), "00000000-0000-0000-0000-000000000000"); + EXPECT_EQ(UUID::NULL_UUID.ToString(), "00000000-0000-0000-0000-000000000000"); } /****************************************************************************************/ @@ -169,14 +173,39 @@ TEST_F(UUIDFixture, GeneratingFiveMillionUniqueID) /****************************************************************************************/ -TEST_F(UUIDFixture, Raw_ShouldGiveBackTheRawDataOfTheID) +TEST_F(UUIDFixture, ToBytes_ShouldGiveBackTheRawDataOfTheID) { - std::span d = m_d; + std::span idBytes = m_d.ToBytes(); std::span raw = m_raw; + EXPECT_THAT(idBytes, ::testing::ElementsAreArray(raw)); +} + +/****************************************************************************************/ + +TEST_F(UUIDFixture, Pack_ShouldGiveAFlatUUIDWithTheSameRawBytes) +{ + const ::Flat::Bigfoot::UUID flatUUID = flatbuffers::Pack(m_d); + for (std::uint32_t i = 0; i < UUID::UUID_BYTE_SIZE; ++i) { - EXPECT_EQ(d[i], raw[i]); + EXPECT_EQ(std::byte {(*flatUUID.bytes())[i]}, m_raw[i]); } } + +/****************************************************************************************/ + +TEST_F(UUIDFixture, UnPack_ShouldGiveBackTheOriginalUUID) +{ + const ::Flat::Bigfoot::UUID flatUUID = flatbuffers::Pack(m_d); + EXPECT_EQ(flatbuffers::UnPack(flatUUID), m_d); +} + +/****************************************************************************************/ + +TEST_F(UUIDFixture, FormatsUUID) +{ + EXPECT_STREQ(Format("{}", m_b).c_str(), m_b.ToString().c_str()); + EXPECT_STREQ(Format("{}", UUID::NULL_UUID).c_str(), "00000000-0000-0000-0000-000000000000"); +} } // 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/Include/SasquatchBakerTests/BigFileInfo_generated.hpp b/Bigfoot/Tests/Tools/SasquatchBaker/Include/SasquatchBakerTests/BigFileInfo_generated.hpp new file mode 100644 index 0000000..b19d7b9 --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/Include/SasquatchBakerTests/BigFileInfo_generated.hpp @@ -0,0 +1,22 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file BigFileInfo.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_SASQUATCHBAKERTESTS_BIGFILEINFO_GENERATED_HPP +#define BIGFOOT_SASQUATCHBAKERTESTS_BIGFILEINFO_GENERATED_HPP + +#include + +#if defined BIGFOOT_TOOLMODE + +namespace Bigfoot +{ +/* + * BigFile location + */ +constexpr eastl::string_view BIGFILE_SASQUATCHBAKERTESTS_LOCATION {"./SasquatchBakerTests.db"}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp new file mode 100644 index 0000000..4a39381 --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBaker.cpp @@ -0,0 +1,92 @@ +/********************************************************************* + * \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 + { + m_bigFile.Clear(); + m_outputFile.Delete(); + } + + /* + * Packs an asset and saves it to the BigFile + * + * \param p_asset The asset to save + * \return The UUID the asset was saved under + */ + template + UUID SaveAsset(ASSET& p_asset) + { + const eastl::vector flatbuffer = p_asset.Pack(); + m_bigFile.SaveAssetFlatbuffer(p_asset.GetHeader().uuid, flatbuffer, p_asset.GetHardReferences(flatbuffer)); + return p_asset.GetHeader().uuid; + } + + static constexpr eastl::string_view SASQUATCH_SASQUATCHBAKERTESTS_LOCATION {"./SasquatchBakerTests.sasquatch"}; + BigFile m_bigFile {File {BIGFILE_SASQUATCHBAKERTESTS_LOCATION}}; + File m_outputFile {SASQUATCH_SASQUATCHBAKERTESTS_LOCATION}; +}; + +/****************************************************************************************/ + +TEST_F(SasquatchBakerFixture, Bake_ShouldWriteAnEmptyFileWhenTheBigFileIsEmpty) +{ + Bake(m_bigFile, m_outputFile); + Sasquatch sasquatch {m_outputFile}; + + const eastl::vector data = m_outputFile.Read(0, m_outputFile.GetSize()); + ASSERT_EQ(m_outputFile.GetSize(), sizeof(SasquatchHeader)); + + EXPECT_EQ(sasquatch.GetHeader().m_magic, SasquatchHeader::MAGIC); + EXPECT_EQ(sasquatch.GetHeader().m_version, SasquatchHeader::VERSION); + EXPECT_EQ(sasquatch.GetHeader().m_assetCount, 0); +} + +/****************************************************************************************/ + +TEST_F(SasquatchBakerFixture, Bake_ShouldWriteEveryAssetOfTheBigFile) +{ + AssetA assetA; + assetA.SetName("hello"); + SaveAsset(assetA); + + AssetB assetB; + assetB.SetName("world"); + assetB.GetAsset().ref_a = HardReference {assetA.GetHeader().uuid}; + SaveAsset(assetB); + + Bake(m_bigFile, m_outputFile); + Sasquatch sasquatch {m_outputFile}; + + EXPECT_EQ(sasquatch.GetHeader().m_magic, SasquatchHeader::MAGIC); + EXPECT_EQ(sasquatch.GetHeader().m_version, SasquatchHeader::VERSION); + EXPECT_EQ(sasquatch.GetHeader().m_assetCount, 2); + + EXPECT_FALSE(sasquatch.GetAssetFlatbuffer(assetA.GetHeader().uuid).empty()); + EXPECT_FALSE(sasquatch.GetAssetFlatbuffer(assetB.GetHeader().uuid).empty()); + + EXPECT_FALSE(sasquatch.GetAssetHardReferences(assetB.GetHeader().uuid).empty()); +} +} // 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..d8fe0b5 --- /dev/null +++ b/Bigfoot/Tests/Tools/SasquatchBaker/SasquatchBakerTests/main.cpp @@ -0,0 +1,21 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include +#include + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + + GTEST_FLAG_SET(death_test_style, "threadsafe"); + + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime m_lifetime); + + return RUN_ALL_TESTS(); +} diff --git a/Bigfoot/Tests/Utils/Algorithm/Graph.cpp b/Bigfoot/Tests/Utils/Algorithm/Graph.cpp new file mode 100644 index 0000000..c20f248 --- /dev/null +++ b/Bigfoot/Tests/Utils/Algorithm/Graph.cpp @@ -0,0 +1,79 @@ +/********************************************************************* + * \file Graph.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +namespace Bigfoot +{ +class LayeredTopologicalSortFixture: public ::testing::Test +{ + protected: + ankerl::unordered_dense::map> m_graph; + ankerl::unordered_dense::set m_nodes; + eastl::vector> m_waves; + eastl::vector m_cyclicNodes; +}; + +/****************************************************************************************/ + +TEST_F(LayeredTopologicalSortFixture, ShouldOrderEachNodeInAnEarlierWaveThanTheNodesThatDependOnIt) +{ + m_graph = {{0, {1, 2}}, {1, {3}}, {2, {3}}, {3, {}}}; + m_nodes = {0, 1, 2, 3}; + + EXPECT_FALSE(LayeredTopologicalSort(m_graph, m_nodes, m_waves, m_cyclicNodes)); + + ASSERT_EQ(m_waves.size(), 3); + EXPECT_THAT(m_waves[0], ::testing::UnorderedElementsAre(3)); + EXPECT_THAT(m_waves[1], ::testing::UnorderedElementsAre(1, 2)); + EXPECT_THAT(m_waves[2], ::testing::UnorderedElementsAre(0)); + EXPECT_TRUE(m_cyclicNodes.empty()); +} + +/****************************************************************************************/ + +TEST_F(LayeredTopologicalSortFixture, ShouldOmitNodesThatAreCaughtInACycleWithoutHanging) +{ + m_graph = {{0, {1}}, {1, {0}}}; + m_nodes = {0, 1}; + + EXPECT_TRUE(LayeredTopologicalSort(m_graph, m_nodes, m_waves, m_cyclicNodes)); + + EXPECT_TRUE(m_waves.empty()); + EXPECT_THAT(m_cyclicNodes, ::testing::UnorderedElementsAre(0, 1)); +} + +/****************************************************************************************/ + +TEST_F(LayeredTopologicalSortFixture, ShouldIgnoreEdgesThatPointOutsideOfTheNodesBeingSorted) +{ + m_graph = {{0, {1}}}; + m_nodes = {0}; + + EXPECT_FALSE(LayeredTopologicalSort(m_graph, m_nodes, m_waves, m_cyclicNodes)); + + ASSERT_EQ(m_waves.size(), 1); + EXPECT_THAT(m_waves[0], ::testing::ElementsAre(0)); + EXPECT_TRUE(m_cyclicNodes.empty()); +} + +/****************************************************************************************/ + +TEST_F(LayeredTopologicalSortFixture, ShouldReportNodesThatOnlyDependOnACycleAsCyclicToo) +{ + // 0 <-> 1 is the cycle; 2 depends on 0 so it can never resolve either, even though it isn't part of the cycle. + m_graph = {{0, {1}}, {1, {0}}, {2, {0}}}; + m_nodes = {0, 1, 2}; + + EXPECT_TRUE(LayeredTopologicalSort(m_graph, m_nodes, m_waves, m_cyclicNodes)); + + EXPECT_TRUE(m_waves.empty()); + EXPECT_THAT(m_cyclicNodes, ::testing::UnorderedElementsAre(0, 1, 2)); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/Assert/Assert.cpp b/Bigfoot/Tests/Utils/Assert/Assert.cpp new file mode 100644 index 0000000..d9b9ef9 --- /dev/null +++ b/Bigfoot/Tests/Utils/Assert/Assert.cpp @@ -0,0 +1,33 @@ +/********************************************************************* + * \file Assert.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include + +namespace Bigfoot +{ +using TestAssertHandler = AssertHandler; + +/****************************************************************************************/ + +TEST(AssertDeathTest, Assert_ShouldTerminateTheProcessWhenTheConditionIsFalse) +{ + EXPECT_DEATH({ ASSERT(TestAssertHandler, false, "Something went wrong: {}", 42); }, ""); +} + +/****************************************************************************************/ + +TEST(AssertDeathTest, CriticalAssert_ShouldTerminateTheProcessWhenTheConditionIsFalse) +{ + EXPECT_DEATH({ CRITICAL_ASSERT(TestAssertHandler, false, "Something critical went wrong: {}", 42); }, ""); +} +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Tests/Utils/Assert/AssertHandler.cpp b/Bigfoot/Tests/Utils/Assert/AssertHandler.cpp new file mode 100644 index 0000000..c267eef --- /dev/null +++ b/Bigfoot/Tests/Utils/Assert/AssertHandler.cpp @@ -0,0 +1,62 @@ +/********************************************************************* + * \file AssertHandler.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include +#include + +#include +#include +#include +#include + +namespace Bigfoot +{ +class AssertHandlerFixture: public ::testing::Test +{ + protected: + static std::string ReadFileSinkContent() + { + Singleton::GetInstance().Flush(); + + std::ifstream file {std::string {Log::FILE_SINK_PATH.data()}}; + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } +}; + +/****************************************************************************************/ + +TEST_F(AssertHandlerFixture, Handle_ShouldLogAFatalMessageContainingTheGivenMessage) +{ + constexpr std::source_location location = std::source_location::current(); + AssertHandler::Handle(location, Format("Something went wrong: {}", 42)); + + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Something went wrong: 42")); +} + +/****************************************************************************************/ + +TEST_F(AssertHandlerFixture, Handle_ShouldLogAFatalMessageContainingTheSourceLocation) +{ + constexpr std::source_location location = std::source_location::current(); + AssertHandler::Handle(location, "Doesn't matter"); + + const std::string content = ReadFileSinkContent(); + EXPECT_THAT(content, ::testing::HasSubstr(location.file_name())); + EXPECT_THAT(content, ::testing::HasSubstr(location.function_name())); +} +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Tests/Utils/CMakeLists.txt b/Bigfoot/Tests/Utils/CMakeLists.txt index 47c3d4b..d6c67dd 100644 --- a/Bigfoot/Tests/Utils/CMakeLists.txt +++ b/Bigfoot/Tests/Utils/CMakeLists.txt @@ -1,11 +1,12 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}Tests) -set(BigfootDependencies - Utils) +set(Dependencies) +set(BigfootDependencies) bigfoot_create_package_tests( - "" - "${BigfootDependencies}") + "${Dependencies}" + "${BigfootDependencies}" + "") -bigfoot_setup_dependencies("Tests/Bigfoot") \ No newline at end of file +bigfoot_create_logger() \ No newline at end of file diff --git a/Bigfoot/Tests/Utils/Containers/SlotMap.cpp b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp new file mode 100644 index 0000000..6296592 --- /dev/null +++ b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp @@ -0,0 +1,1445 @@ +/********************************************************************* + * \file SlotMap.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +namespace Bigfoot +{ +class SlotMapKeyFixture: public ::testing::Test +{ + protected: + using SlotMapKey = SlotMapKey<>; +}; + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, INVALID_VERSION_IsZero) +{ + EXPECT_EQ(SlotMapKey::INVALID_VERSION, 0); +} + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, MAX_INDEX_IsMaxU32) +{ + EXPECT_EQ(SlotMapKey::MAX_INDEX, std::numeric_limits::max()); +} + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, Valid_ShouldReturnFalseIfVersionIsInvalid) +{ + EXPECT_FALSE(SlotMapKey().Valid()); + EXPECT_FALSE(SlotMapKey(0, 0).Valid()); + EXPECT_FALSE(SlotMapKey(SlotMapKey::INVALID_VERSION, 0).Valid()); + EXPECT_FALSE(SlotMapKey(0, 42).Valid()); + EXPECT_FALSE(SlotMapKey(SlotMapKey::INVALID_VERSION, 42).Valid()); +} + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, Valid_ShouldReturnTrueIfVersionisNotInvalid) +{ + EXPECT_TRUE(SlotMapKey(1, 0).Valid()); + EXPECT_TRUE(SlotMapKey(1, 42).Valid()); +} + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, GetVersion_ShouldReturnTheVersion) +{ + EXPECT_EQ(SlotMapKey(0, 0).GetVersion(), 0); + EXPECT_EQ(SlotMapKey(42, 69).GetVersion(), 42); +} + +/****************************************************************************************/ + +TEST_F(SlotMapKeyFixture, GetIndex_ShouldReturnTheIndex) +{ + EXPECT_EQ(SlotMapKey(0, 0).GetIndex(), 0); + EXPECT_EQ(SlotMapKey(42, 69).GetIndex(), 69); +} + +/****************************************************************************************/ + +class SlotMapValueMock +{ + public: + SlotMapValueMock() = default; + virtual ~SlotMapValueMock() = default; + + SlotMapValueMock(const SlotMapValueMock&) = delete; + SlotMapValueMock(SlotMapValueMock&&) = delete; + + virtual void Destruct() = 0; + + SlotMapValueMock& operator=(const SlotMapValueMock&) = delete; + SlotMapValueMock& operator=(SlotMapValueMock&&) = delete; +}; + +class SlotMapValue +{ + public: + inline static SlotMapValueMock* ms_mock = nullptr; + + SlotMapValue() = default; + + SlotMapValue(const std::uint32_t p_value): + m_value(p_value) + { + } + + SlotMapValue(const SlotMapValue& p_value) = default; + SlotMapValue(SlotMapValue&& p_value) = default; + + ~SlotMapValue() + { + if (ms_mock) + { + ms_mock->Destruct(); + } + } + + SlotMapValue& operator=(const SlotMapValue& p_value) = default; + SlotMapValue& operator=(SlotMapValue&& p_value) = default; + + operator std::uint32_t() const + { + return m_value; + } + + private: + std::uint32_t m_value = 0; +}; + +template +class SlotMapFixture: public ::testing::Test +{ + protected: + class SlotMapValueMockImpl: public SlotMapValueMock + { + public: + MOCK_METHOD(void, Destruct, (), (override)); + }; + + using SlotMapKey = SLOTMAP::SlotMapKey; + + std::unique_ptr m_mock; + SLOTMAP m_slotMap; + + void SetUp() override + { + m_mock = std::make_unique<::testing::NiceMock>(); + SlotMapValue::ms_mock = m_mock.get(); + } + + void TearDown() override + { + SlotMapValue::ms_mock = nullptr; + m_mock.reset(); + } +}; + +class SlotMapNameGenerator +{ + public: + template + static std::string GetName([[maybe_unused]] int p_index) + { + if constexpr (std::is_same_v>>) + { + return "DenseSlotMap"; + } + if constexpr (std::is_same_v>>) + { + return "StableSlotMap"; + } + } +}; + +using SlotMapTypes = ::testing::Types>, + StableSlotMap>>; +TYPED_TEST_SUITE(SlotMapFixture, SlotMapTypes, SlotMapNameGenerator); + +// DenseSlotMap has no notion of pages, so the shared TYPED_TEST bodies below can't reference TypeParam::PAGE_SIZE +constexpr std::uint32_t PAGE_SIZE = StableSlotMap>::PAGE_SIZE; + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Has_ReturnTrueIfSlotMapHasTheKey) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key = this->m_slotMap.Insert(42); + EXPECT_TRUE(this->m_slotMap.Has(key)); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Has_ReturnFalseIfSlotMapDoesNotHaveTheKey) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + std::ignore = this->m_slotMap.Insert(42); + EXPECT_FALSE(this->m_slotMap.Has(SlotMapKey {1, 23})); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Has_ReturnFalseForStaleKey) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey staleKey = this->m_slotMap.Insert(42); + this->m_slotMap.Remove(staleKey); + std::ignore = this->m_slotMap.Insert(69); + + EXPECT_FALSE(this->m_slotMap.Has(staleKey)); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Size_ShouldReturnTheSizeOfTheSlotMap) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + EXPECT_EQ(this->m_slotMap.Size(), 0); + const SlotMapKey key1 = this->m_slotMap.Insert(42); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + EXPECT_EQ(this->m_slotMap.Size(), 2); + this->m_slotMap.Remove(key1); + EXPECT_EQ(this->m_slotMap.Size(), 1); + this->m_slotMap.Remove(key2); + EXPECT_EQ(this->m_slotMap.Size(), 0); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Empty_ShouldReturnTrueIfTheSlotMapIsEmpty) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + EXPECT_TRUE(this->m_slotMap.Empty()); + const SlotMapKey key1 = this->m_slotMap.Insert(42); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + this->m_slotMap.Remove(key1); + this->m_slotMap.Remove(key2); + EXPECT_TRUE(this->m_slotMap.Empty()); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Empty_ShouldReturnFalseIfTheSlotMapIsNotEmpty) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key1 = this->m_slotMap.Insert(42); + EXPECT_FALSE(this->m_slotMap.Empty()); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + EXPECT_FALSE(this->m_slotMap.Empty()); + this->m_slotMap.Remove(key1); + EXPECT_FALSE(this->m_slotMap.Empty()); + this->m_slotMap.Remove(key2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Insert_ShouldReturnAValidSlotKey) +{ + EXPECT_TRUE(this->m_slotMap.Insert(42).Valid()); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Insert_ShouldRecycleIndicesWhenPossible) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key1 = this->m_slotMap.Insert(42); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + + this->m_slotMap.Remove(key1); + this->m_slotMap.Remove(key2); + + const SlotMapKey key11 = this->m_slotMap.Insert(42); + const SlotMapKey key21 = this->m_slotMap.Insert(69); + + EXPECT_EQ(key1.GetVersion() + 1, key11.GetVersion()); + EXPECT_EQ(key1.GetIndex(), key21.GetIndex()); // LIFO + + EXPECT_EQ(key2.GetVersion() + 1, key21.GetVersion()); + EXPECT_EQ(key2.GetIndex(), key11.GetIndex()); // LIFO +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Get_ShouldReturnTheValue) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key = this->m_slotMap.Insert(42); + EXPECT_EQ(*this->m_slotMap.Get(key), 42); + EXPECT_EQ(*std::as_const(this->m_slotMap).Get(key), 42); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Get_ShouldNullptrIfTheValueIsNotInMap) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key {1, 0}; + EXPECT_EQ(this->m_slotMap.Get(key), nullptr); + EXPECT_EQ(std::as_const(this->m_slotMap).Get(key), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Get_ShouldReturnNullptrForStaleKey) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey staleKey = this->m_slotMap.Insert(42); + this->m_slotMap.Remove(staleKey); + std::ignore = this->m_slotMap.Insert(69); + + EXPECT_EQ(this->m_slotMap.Get(staleKey), nullptr); + EXPECT_EQ(std::as_const(this->m_slotMap).Get(staleKey), nullptr); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Remove_ShouldRemoveTheValue) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key = this->m_slotMap.Insert(42); + EXPECT_TRUE(this->m_slotMap.Has(key)); + + EXPECT_CALL(*this->m_mock, Destruct()).Times(1); + this->m_slotMap.Remove(key); + EXPECT_FALSE(this->m_slotMap.Has(key)); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Remove_ShouldBeANoOpForStaleKey) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey staleKey = this->m_slotMap.Insert(42); + this->m_slotMap.Remove(staleKey); + const SlotMapKey freshKey = this->m_slotMap.Insert(69); + + this->m_slotMap.Remove(staleKey); + + EXPECT_TRUE(this->m_slotMap.Has(freshKey)); + EXPECT_EQ(*this->m_slotMap.Get(freshKey), 69); + EXPECT_EQ(this->m_slotMap.Size(), 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Remove_ExhaustingAllVersionsOfKeyMarksItDeadForGood) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + for (typename SlotMapKey::VersionType version = 0; version < SlotMapKey::MAX_VERSION; ++version) + { + const SlotMapKey key = this->m_slotMap.Insert(42); + EXPECT_EQ(key.GetIndex(), 0); + EXPECT_EQ(key.GetVersion(), version + 1); + this->m_slotMap.Remove(key); + } + EXPECT_TRUE(this->m_slotMap.Empty()); + const SlotMapKey key = this->m_slotMap.Insert(42); + EXPECT_EQ(key.GetIndex(), 1); + EXPECT_EQ(key.GetVersion(), 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Reset_ShouldClearTheMapAndOldKeysAreValid) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key1 = this->m_slotMap.Insert(42); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + + EXPECT_CALL(*this->m_mock, Destruct()).Times(2); + this->m_slotMap.Reset(); + + EXPECT_EQ(this->m_slotMap.Size(), 0); + EXPECT_TRUE(this->m_slotMap.Empty()); + + EXPECT_FALSE(this->m_slotMap.Has(key1)); + EXPECT_FALSE(this->m_slotMap.Has(key2)); + + EXPECT_EQ(this->m_slotMap.Insert(42), key1); + EXPECT_EQ(this->m_slotMap.Insert(69), key2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Clear_ShouldClearTheMapAndOldKeysAreInvalid) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const SlotMapKey key1 = this->m_slotMap.Insert(42); + const SlotMapKey key2 = this->m_slotMap.Insert(69); + + EXPECT_CALL(*this->m_mock, Destruct()).Times(2); + this->m_slotMap.Clear(); + + EXPECT_EQ(this->m_slotMap.Size(), 0); + EXPECT_TRUE(this->m_slotMap.Empty()); + + EXPECT_FALSE(this->m_slotMap.Has(key1)); + EXPECT_FALSE(this->m_slotMap.Has(key2)); + + EXPECT_NE(this->m_slotMap.Insert(42), key1); + EXPECT_NE(this->m_slotMap.Insert(69), key2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldTraverseInInsertionOrder) +{ + EXPECT_EQ(this->m_slotMap.begin(), this->m_slotMap.end()); + + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + std::uint32_t expected = 1; + for (auto it = this->m_slotMap.begin(); it != this->m_slotMap.end(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSupportPreAndPostIncrementDecrement) +{ + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Pre-increment advances and returns *this + auto preIt = this->m_slotMap.begin(); + auto& preIncrementResult = ++preIt; + EXPECT_EQ(&preIncrementResult, &preIt); + EXPECT_EQ(*preIt, 2); + + // Post-increment advances but returns the old value + auto postIt = this->m_slotMap.begin(); + const auto postIncrementResult = postIt++; + EXPECT_EQ(*postIncrementResult, 1); + EXPECT_EQ(*postIt, 2); + + // Pre-decrement retreats and returns *this + auto preDecIt = this->m_slotMap.end(); + --preDecIt; // now at 3 + auto& preDecrementResult = --preDecIt; // now at 2 + EXPECT_EQ(&preDecrementResult, &preDecIt); + EXPECT_EQ(*preDecIt, 2); + + // Post-decrement retreats but returns the old value + auto postDecIt = this->m_slotMap.end(); + --postDecIt; // now at 3 + const auto postDecrementResult = postDecIt--; + EXPECT_EQ(*postDecrementResult, 3); + EXPECT_EQ(*postDecIt, 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSkipRemovedSlotsDuringTraversal) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + std::ignore = this->m_slotMap.Insert(1); + const SlotMapKey key2 = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + this->m_slotMap.Remove(key2); + + std::size_t count = 0; + std::uint32_t sum = 0; + for (const std::uint32_t value: this->m_slotMap) + { + ++count; + sum += value; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldTraverseAcrossMultiplePages) +{ + // StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + std::uint32_t expected = 0; + for (const std::uint32_t value: this->m_slotMap) + { + EXPECT_EQ(value, expected); + ++expected; + } + EXPECT_EQ(expected, elementCount); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldCrossThePageBoundaryForwardAndBackward) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto boundaryIt = this->m_slotMap.begin(); + for (std::uint32_t step = 0; step < PAGE_SIZE - 1; ++step) + { + ++boundaryIt; + } + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); + ++boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE); + --boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSupportPreAndPostIncrementDecrementAcrossTheBoundary) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto preBoundaryIt = this->m_slotMap.begin(); + auto& preBoundaryIncrementResult = ++preBoundaryIt; + EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt); + EXPECT_EQ(*preBoundaryIt, 1); + + auto postBoundaryIt = this->m_slotMap.begin(); + const auto postBoundaryIncrementResult = postBoundaryIt++; + EXPECT_EQ(*postBoundaryIncrementResult, 0); + EXPECT_EQ(*postBoundaryIt, 1); + + auto preBoundaryDecIt = this->m_slotMap.end(); + --preBoundaryDecIt; // now at 99 + auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 98 + EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt); + EXPECT_EQ(*preBoundaryDecIt, 98); + + auto postBoundaryDecIt = this->m_slotMap.end(); + --postBoundaryDecIt; // now at 99 + const auto postBoundaryDecrementResult = postBoundaryDecIt--; + EXPECT_EQ(*postBoundaryDecrementResult, 99); + EXPECT_EQ(*postBoundaryDecIt, 98); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSkipHolesStraddlingThePageBoundary) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array keys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + keys[value] = this->m_slotMap.Insert(value); + } + + this->m_slotMap.Remove(keys[PAGE_SIZE - 1]); + this->m_slotMap.Remove(keys[PAGE_SIZE]); + + std::size_t count = 0; + for (const std::uint32_t value: this->m_slotMap) + { + EXPECT_NE(value, PAGE_SIZE - 1); + EXPECT_NE(value, PAGE_SIZE); + ++count; + } + EXPECT_EQ(count, elementCount - 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSkipAFullyEmptyLeadingPage) +{ + // A fully empty leading page must be skipped by begin()/SeekFirstFrom + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array leadingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + leadingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = 0; value < PAGE_SIZE; ++value) + { + this->m_slotMap.Remove(leadingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (const std::uint32_t value: this->m_slotMap) + { + ++count; + sum += value; + } + EXPECT_EQ(count, elementCount - PAGE_SIZE); + EXPECT_EQ(sum, 2934U); // 64 + 65 + ... + 99 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, Iterator_ShouldSkipAFullyEmptyTrailingPage) +{ + // A fully empty trailing page must be skipped when retreating past it + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array trailingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + trailingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = PAGE_SIZE; value < elementCount; ++value) + { + this->m_slotMap.Remove(trailingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (const std::uint32_t value: this->m_slotMap) + { + ++count; + sum += value; + } + EXPECT_EQ(count, PAGE_SIZE); + EXPECT_EQ(sum, 2016U); // 0 + 1 + ... + 63 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldTraverseInInsertionOrder) +{ + const TypeParam& constMap = this->m_slotMap; + + // const_iterator is reachable two ways: cbegin()/cend(), or begin()/end() on a const object + EXPECT_EQ(this->m_slotMap.cbegin(), this->m_slotMap.cend()); + EXPECT_EQ(constMap.begin(), constMap.end()); + + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + std::uint32_t expected = 1; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } + + expected = 1; + for (auto it = constMap.begin(); it != constMap.end(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSupportPreAndPostIncrementDecrement) +{ + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Pre-increment advances and returns *this + auto preIt = this->m_slotMap.cbegin(); + auto& preIncrementResult = ++preIt; + EXPECT_EQ(&preIncrementResult, &preIt); + EXPECT_EQ(*preIt, 2); + + // Post-increment advances but returns the old value + auto postIt = this->m_slotMap.cbegin(); + const auto postIncrementResult = postIt++; + EXPECT_EQ(*postIncrementResult, 1); + EXPECT_EQ(*postIt, 2); + + // Pre-decrement retreats and returns *this + auto preDecIt = this->m_slotMap.cend(); + --preDecIt; // now at 3 + auto& preDecrementResult = --preDecIt; // now at 2 + EXPECT_EQ(&preDecrementResult, &preDecIt); + EXPECT_EQ(*preDecIt, 2); + + // Post-decrement retreats but returns the old value + auto postDecIt = this->m_slotMap.cend(); + --postDecIt; // now at 3 + const auto postDecrementResult = postDecIt--; + EXPECT_EQ(*postDecrementResult, 3); + EXPECT_EQ(*postDecIt, 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSkipRemovedSlotsDuringTraversal) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const TypeParam& constMap = this->m_slotMap; + + std::ignore = this->m_slotMap.Insert(1); + const SlotMapKey key2 = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Removed slots are skipped identically through cbegin()/cend() and the const-object overloads + this->m_slotMap.Remove(key2); + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); + + count = 0; + sum = 0; + for (auto it = constMap.begin(); it != constMap.end(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldTraverseAcrossMultiplePages) +{ + // StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + std::uint32_t expected = 0; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + EXPECT_EQ(*it, expected); + ++expected; + } + EXPECT_EQ(expected, elementCount); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldCrossThePageBoundaryForwardAndBackward) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto boundaryIt = this->m_slotMap.cbegin(); + for (std::uint32_t step = 0; step < PAGE_SIZE - 1; ++step) + { + ++boundaryIt; + } + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); + ++boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE); + --boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSupportPreAndPostIncrementDecrementAcrossTheBoundary) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto preBoundaryIt = this->m_slotMap.cbegin(); + auto& preBoundaryIncrementResult = ++preBoundaryIt; + EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt); + EXPECT_EQ(*preBoundaryIt, 1); + + auto postBoundaryIt = this->m_slotMap.cbegin(); + const auto postBoundaryIncrementResult = postBoundaryIt++; + EXPECT_EQ(*postBoundaryIncrementResult, 0); + EXPECT_EQ(*postBoundaryIt, 1); + + auto preBoundaryDecIt = this->m_slotMap.cend(); + --preBoundaryDecIt; // now at 99 + auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 98 + EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt); + EXPECT_EQ(*preBoundaryDecIt, 98); + + auto postBoundaryDecIt = this->m_slotMap.cend(); + --postBoundaryDecIt; // now at 99 + const auto postBoundaryDecrementResult = postBoundaryDecIt--; + EXPECT_EQ(*postBoundaryDecrementResult, 99); + EXPECT_EQ(*postBoundaryDecIt, 98); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSkipHolesStraddlingThePageBoundary) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array keys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + keys[value] = this->m_slotMap.Insert(value); + } + + this->m_slotMap.Remove(keys[PAGE_SIZE - 1]); + this->m_slotMap.Remove(keys[PAGE_SIZE]); + + std::size_t count = 0; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + EXPECT_NE(*it, PAGE_SIZE - 1); + EXPECT_NE(*it, PAGE_SIZE); + ++count; + } + EXPECT_EQ(count, elementCount - 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSkipAFullyEmptyLeadingPage) +{ + // A fully empty leading page must be skipped by cbegin()/SeekFirstFrom + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array leadingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + leadingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = 0; value < PAGE_SIZE; ++value) + { + this->m_slotMap.Remove(leadingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, elementCount - PAGE_SIZE); + EXPECT_EQ(sum, 2934U); // 64 + 65 + ... + 99 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstIterator_ShouldSkipAFullyEmptyTrailingPage) +{ + // A fully empty trailing page must be skipped when retreating past it + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array trailingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + trailingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = PAGE_SIZE; value < elementCount; ++value) + { + this->m_slotMap.Remove(trailingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, PAGE_SIZE); + EXPECT_EQ(sum, 2016U); // 0 + 1 + ... + 63 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldTraverseInReverseInsertionOrder) +{ + EXPECT_EQ(this->m_slotMap.rbegin(), this->m_slotMap.rend()); + + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + std::uint32_t expected = 3; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + EXPECT_EQ(*it, expected); + --expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSupportPreAndPostIncrementDecrement) +{ + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Pre-increment advances and returns *this + auto preIt = this->m_slotMap.rbegin(); + auto& preIncrementResult = ++preIt; + EXPECT_EQ(&preIncrementResult, &preIt); + EXPECT_EQ(*preIt, 2); + + // Post-increment advances but returns the old value + auto postIt = this->m_slotMap.rbegin(); + const auto postIncrementResult = postIt++; + EXPECT_EQ(*postIncrementResult, 3); + EXPECT_EQ(*postIt, 2); + + // Pre-decrement retreats and returns *this + auto preDecIt = this->m_slotMap.rend(); + --preDecIt; // now at 1 + auto& preDecrementResult = --preDecIt; // now at 2 + EXPECT_EQ(&preDecrementResult, &preDecIt); + EXPECT_EQ(*preDecIt, 2); + + // Post-decrement retreats but returns the old value + auto postDecIt = this->m_slotMap.rend(); + --postDecIt; // now at 1 + const auto postDecrementResult = postDecIt--; + EXPECT_EQ(*postDecrementResult, 1); + EXPECT_EQ(*postDecIt, 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSkipRemovedSlotsDuringTraversal) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + std::ignore = this->m_slotMap.Insert(1); + const SlotMapKey key2 = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + this->m_slotMap.Remove(key2); + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldTraverseAcrossMultiplePages) +{ + // StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + std::uint32_t expected = elementCount - 1; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + EXPECT_EQ(*it, expected); + --expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldCrossThePageBoundaryForwardAndBackward) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + // Explicitly cross the page boundary (value 64 -> 63) forward and backward + auto boundaryIt = this->m_slotMap.rbegin(); + while (*boundaryIt != PAGE_SIZE) + { + ++boundaryIt; + } + ++boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); + --boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSupportPreAndPostIncrementDecrementAcrossTheBoundary) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto preBoundaryIt = this->m_slotMap.rbegin(); + auto& preBoundaryIncrementResult = ++preBoundaryIt; + EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt); + EXPECT_EQ(*preBoundaryIt, 98); + + auto postBoundaryIt = this->m_slotMap.rbegin(); + const auto postBoundaryIncrementResult = postBoundaryIt++; + EXPECT_EQ(*postBoundaryIncrementResult, 99); + EXPECT_EQ(*postBoundaryIt, 98); + + auto preBoundaryDecIt = this->m_slotMap.rend(); + --preBoundaryDecIt; // now at 0 + auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 1 + EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt); + EXPECT_EQ(*preBoundaryDecIt, 1); + + auto postBoundaryDecIt = this->m_slotMap.rend(); + --postBoundaryDecIt; // now at 0 + const auto postBoundaryDecrementResult = postBoundaryDecIt--; + EXPECT_EQ(*postBoundaryDecrementResult, 0); + EXPECT_EQ(*postBoundaryDecIt, 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSkipHolesStraddlingThePageBoundary) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array keys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + keys[value] = this->m_slotMap.Insert(value); + } + + this->m_slotMap.Remove(keys[PAGE_SIZE - 1]); + this->m_slotMap.Remove(keys[PAGE_SIZE]); + + std::size_t count = 0; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + EXPECT_NE(*it, PAGE_SIZE - 1); + EXPECT_NE(*it, PAGE_SIZE); + ++count; + } + EXPECT_EQ(count, elementCount - 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSkipAFullyEmptyLeadingPage) +{ + // A fully empty leading page must be skipped when retreating past it (rend() side) + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array leadingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + leadingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = 0; value < PAGE_SIZE; ++value) + { + this->m_slotMap.Remove(leadingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, elementCount - PAGE_SIZE); + EXPECT_EQ(sum, 2934U); // 64 + 65 + ... + 99 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ReverseIterator_ShouldSkipAFullyEmptyTrailingPage) +{ + // A fully empty trailing page must be skipped by rbegin()/SeekLastFrom + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array trailingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + trailingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = PAGE_SIZE; value < elementCount; ++value) + { + this->m_slotMap.Remove(trailingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, PAGE_SIZE); + EXPECT_EQ(sum, 2016U); // 0 + 1 + ... + 63 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldTraverseInReverseInsertionOrder) +{ + const TypeParam& constMap = this->m_slotMap; + + // const_reverse_iterator is reachable two ways: crbegin()/crend(), or rbegin()/rend() on a + // const object + EXPECT_EQ(this->m_slotMap.crbegin(), this->m_slotMap.crend()); + EXPECT_EQ(constMap.rbegin(), constMap.rend()); + + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + std::uint32_t expected = 3; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + EXPECT_EQ(*it, expected); + --expected; + } + + expected = 3; + for (auto it = constMap.rbegin(); it != constMap.rend(); ++it) + { + EXPECT_EQ(*it, expected); + --expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSupportPreAndPostIncrementDecrement) +{ + std::ignore = this->m_slotMap.Insert(1); + std::ignore = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Pre-increment advances and returns *this + auto preIt = this->m_slotMap.crbegin(); + auto& preIncrementResult = ++preIt; + EXPECT_EQ(&preIncrementResult, &preIt); + EXPECT_EQ(*preIt, 2); + + // Post-increment advances but returns the old value + auto postIt = this->m_slotMap.crbegin(); + const auto postIncrementResult = postIt++; + EXPECT_EQ(*postIncrementResult, 3); + EXPECT_EQ(*postIt, 2); + + // Pre-decrement retreats and returns *this + auto preDecIt = this->m_slotMap.crend(); + --preDecIt; // now at 1 + auto& preDecrementResult = --preDecIt; // now at 2 + EXPECT_EQ(&preDecrementResult, &preDecIt); + EXPECT_EQ(*preDecIt, 2); + + // Post-decrement retreats but returns the old value + auto postDecIt = this->m_slotMap.crend(); + --postDecIt; // now at 1 + const auto postDecrementResult = postDecIt--; + EXPECT_EQ(*postDecrementResult, 1); + EXPECT_EQ(*postDecIt, 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSkipRemovedSlotsDuringTraversal) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + const TypeParam& constMap = this->m_slotMap; + + std::ignore = this->m_slotMap.Insert(1); + const SlotMapKey key2 = this->m_slotMap.Insert(2); + std::ignore = this->m_slotMap.Insert(3); + + // Removed slots are skipped identically through crbegin()/crend() and the const-object overloads + this->m_slotMap.Remove(key2); + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); + + count = 0; + sum = 0; + for (auto it = constMap.rbegin(); it != constMap.rend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, 2); + EXPECT_EQ(sum, 4); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldTraverseAcrossMultiplePages) +{ + // StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + std::uint32_t expected = elementCount - 1; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + EXPECT_EQ(*it, expected); + --expected; + } +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldCrossThePageBoundaryForwardAndBackward) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto boundaryIt = this->m_slotMap.crbegin(); + while (*boundaryIt != PAGE_SIZE) + { + ++boundaryIt; + } + ++boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE - 1); + --boundaryIt; + EXPECT_EQ(*boundaryIt, PAGE_SIZE); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSupportPreAndPostIncrementDecrementAcrossTheBoundary) +{ + constexpr std::uint32_t elementCount = 100; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + std::ignore = this->m_slotMap.Insert(value); + } + + auto preBoundaryIt = this->m_slotMap.crbegin(); + auto& preBoundaryIncrementResult = ++preBoundaryIt; + EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt); + EXPECT_EQ(*preBoundaryIt, 98); + + auto postBoundaryIt = this->m_slotMap.crbegin(); + const auto postBoundaryIncrementResult = postBoundaryIt++; + EXPECT_EQ(*postBoundaryIncrementResult, 99); + EXPECT_EQ(*postBoundaryIt, 98); + + auto preBoundaryDecIt = this->m_slotMap.crend(); + --preBoundaryDecIt; // now at 0 + auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 1 + EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt); + EXPECT_EQ(*preBoundaryDecIt, 1); + + auto postBoundaryDecIt = this->m_slotMap.crend(); + --postBoundaryDecIt; // now at 0 + const auto postBoundaryDecrementResult = postBoundaryDecIt--; + EXPECT_EQ(*postBoundaryDecrementResult, 0); + EXPECT_EQ(*postBoundaryDecIt, 1); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSkipHolesStraddlingThePageBoundary) +{ + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array keys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + keys[value] = this->m_slotMap.Insert(value); + } + + this->m_slotMap.Remove(keys[PAGE_SIZE - 1]); + this->m_slotMap.Remove(keys[PAGE_SIZE]); + + std::size_t count = 0; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + EXPECT_NE(*it, PAGE_SIZE - 1); + EXPECT_NE(*it, PAGE_SIZE); + ++count; + } + EXPECT_EQ(count, elementCount - 2); +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSkipAFullyEmptyLeadingPage) +{ + // A fully empty leading page must be skipped when retreating past it (crend() side) + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array leadingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + leadingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = 0; value < PAGE_SIZE; ++value) + { + this->m_slotMap.Remove(leadingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, elementCount - PAGE_SIZE); + EXPECT_EQ(sum, 2934U); // 64 + 65 + ... + 99 +} + +/****************************************************************************************/ + +TYPED_TEST(SlotMapFixture, ConstReverseIterator_ShouldSkipAFullyEmptyTrailingPage) +{ + // A fully empty trailing page must be skipped by crbegin()/SeekLastFrom + using SlotMapKey = TestFixture::SlotMapKey; + + constexpr std::uint32_t elementCount = 100; + eastl::array trailingKeys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + trailingKeys[value] = this->m_slotMap.Insert(value); + } + for (std::uint32_t value = PAGE_SIZE; value < elementCount; ++value) + { + this->m_slotMap.Remove(trailingKeys[value]); + } + + std::size_t count = 0; + std::uint32_t sum = 0; + for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it) + { + ++count; + sum += *it; + } + EXPECT_EQ(count, PAGE_SIZE); + EXPECT_EQ(sum, 2016U); // 0 + 1 + ... + 63 +} + +/****************************************************************************************/ + +class StableSlotMapFixture: public ::testing::Test +{ + protected: + StableSlotMap m_slotMap; + + using SlotMapKey = StableSlotMap::SlotMapKey; +}; + +/****************************************************************************************/ + +TEST_F(StableSlotMapFixture, PointersAreStable) +{ + const SlotMapKey key = m_slotMap.Insert(42); + const std::uint32_t* const pointer = m_slotMap.Get(key); + + // Inserting enough elements to span multiple pages (PAGE_SIZE is 64) must not move already + // -inserted data: unlike DenseSlotMap's contiguous eastl::vector, growth only appends new + // Page objects (each individually heap-allocated), it never reallocates existing ones. + constexpr std::uint32_t elementCount = 200; + eastl::array keys; + for (std::uint32_t value = 0; value < elementCount; ++value) + { + keys[value] = m_slotMap.Insert(value); + } + + EXPECT_EQ(m_slotMap.Get(key), pointer); + EXPECT_EQ(*pointer, 42); + + // Removing unrelated elements, including ones sharing the same page as key, must not move it + // either: Remove() only destroys that slot in place, it never compacts the page's array. + for (std::uint32_t value = 0; value < elementCount; value += 2) + { + m_slotMap.Remove(keys[value]); + } + + EXPECT_EQ(m_slotMap.Get(key), pointer); + EXPECT_EQ(*pointer, 42); + + // Re-inserting into the freed slots must not move it either + for (std::uint32_t value = 0; value < elementCount / 2; ++value) + { + std::ignore = m_slotMap.Insert(value); + } + + EXPECT_EQ(m_slotMap.Get(key), pointer); + EXPECT_EQ(*pointer, 42); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/Format.cpp b/Bigfoot/Tests/Utils/Format.cpp new file mode 100644 index 0000000..4faccf6 --- /dev/null +++ b/Bigfoot/Tests/Utils/Format.cpp @@ -0,0 +1,48 @@ +/********************************************************************* + * \file Format.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include +#include + +namespace Bigfoot +{ +TEST(Format, ShouldReturnAnEastlString) +{ + const eastl::string result = Format("Hello"); + EXPECT_STREQ(result.c_str(), "Hello"); +} + +/****************************************************************************************/ + +TEST(Format, ShouldFormatASingleArgument) +{ + EXPECT_STREQ(Format("Value: {}", 42).c_str(), "Value: 42"); +} + +/****************************************************************************************/ + +TEST(Format, ShouldFormatMultipleArguments) +{ + EXPECT_STREQ(Format("{} + {} = {}", 1, 2, 3).c_str(), "1 + 2 = 3"); +} + +/****************************************************************************************/ + +TEST(Format, ShouldReturnAnEmptyStringForAnEmptyFormat) +{ + EXPECT_STREQ(Format("").c_str(), ""); +} + +/****************************************************************************************/ + +TEST(Format, ShouldFormatAStringLongerThanTheSmallStringOptimizationBuffer) +{ + const eastl::string expected(256, 'a'); + EXPECT_STREQ(Format("{}", expected.c_str()).c_str(), expected.c_str()); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/Include/UtilsTests/UtilsTestsLogger_generated.hpp b/Bigfoot/Tests/Utils/Include/UtilsTests/UtilsTestsLogger_generated.hpp new file mode 100644 index 0000000..956be17 --- /dev/null +++ b/Bigfoot/Tests/Utils/Include/UtilsTests/UtilsTestsLogger_generated.hpp @@ -0,0 +1,21 @@ +// AUTO-GENERATED DO NOT TOUCH + +/********************************************************************* + * \file UtilsTestsLogger.generated.hpp + * + *********************************************************************/ +#ifndef BIGFOOT_UTILSTESTSLOGGER_GENERATED_HPP +#define BIGFOOT_UTILSTESTSLOGGER_GENERATED_HPP +#include + +#if defined BIGFOOT_NOT_OPTIMIZED + +namespace Bigfoot +{ +/* + * Logger + */ +inline Log::LoggerInfo UTILSTESTS_LOGGER {"UTILSTESTS_LOGGER", Flat::LogLevel::Trace}; +} // namespace Bigfoot +#endif +#endif diff --git a/Bigfoot/Tests/Utils/Log/EASTLFormatters.cpp b/Bigfoot/Tests/Utils/Log/EASTLFormatters.cpp new file mode 100644 index 0000000..5070fc7 --- /dev/null +++ b/Bigfoot/Tests/Utils/Log/EASTLFormatters.cpp @@ -0,0 +1,130 @@ +/********************************************************************* + * \file EASTLFormatters.cpp + * + * \author Romain BOULLARD + * \date August 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Bigfoot +{ +class EASTLFormattersFixture: public ::testing::Test +{ +}; + +/****************************************************************************************/ +TEST_F(EASTLFormattersFixture, FormatsEastlString) +{ + const eastl::string value = "Hello"; + EXPECT_EQ(fmt::format("{}", value), "Hello"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsEastlStringView) +{ + const eastl::string_view value = "Hello"; + EXPECT_EQ(fmt::format("{}", value), "Hello"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsEmptyVectorAsEmptyBrackets) +{ + const eastl::vector value; + EXPECT_EQ(fmt::format("{}", value), "[]"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsSingleElementVectorWithoutASeparator) +{ + const eastl::vector value {42}; + EXPECT_EQ(fmt::format("{}", value), "[42]"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsVectorOfIntsCommaSeparated) +{ + const eastl::vector value {1, 2, 3}; + EXPECT_EQ(fmt::format("{}", value), "[1, 2, 3]"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsVectorOfEastlStrings) +{ + const eastl::vector value {"a", "b"}; + EXPECT_EQ(fmt::format("{}", value), "[a, b]"); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersFixture, FormatsNestedVectors) +{ + const eastl::vector> value {{1, 2}, {3}}; + EXPECT_EQ(fmt::format("{}", value), "[[1, 2], [3]]"); +} + +/****************************************************************************************/ + +#ifdef BIGFOOT_NOT_OPTIMIZED +class EASTLFormattersLogFixture: public ::testing::Test +{ + protected: + static std::string ReadFileSinkContent() + { + Singleton::GetInstance().Flush(); + + std::ifstream file {std::string {Log::FILE_SINK_PATH.data()}}; + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } +}; + +/****************************************************************************************/ + +TEST_F(EASTLFormattersLogFixture, LogsEastlStringArgument) +{ + const eastl::string value = "Hello EASTL string"; + BIGFOOT_LOG_INFO(UTILSTESTS_LOGGER, "{}", value); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello EASTL string")); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersLogFixture, LogsEmptyVectorArgument) +{ + const eastl::vector value; + BIGFOOT_LOG_INFO(UTILSTESTS_LOGGER, "{}", value); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("[]")); +} + +/****************************************************************************************/ + +TEST_F(EASTLFormattersLogFixture, LogsVectorArgumentWithCommaSeparators) +{ + const eastl::vector value {"a", "b", "c"}; + BIGFOOT_LOG_INFO(UTILSTESTS_LOGGER, "{}", value); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("[a, b, c]")); +} +#endif +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/Log/Log.cpp b/Bigfoot/Tests/Utils/Log/Log.cpp new file mode 100644 index 0000000..7bfedeb --- /dev/null +++ b/Bigfoot/Tests/Utils/Log/Log.cpp @@ -0,0 +1,97 @@ +/********************************************************************* + * \file Log.cpp + * + * \author Romain BOULLARD + * \date December 2022 + *********************************************************************/ +#include + +#include + +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include + +namespace Bigfoot +{ +class LogFixture: public ::testing::Test +{ + protected: + static constexpr Flat::LogLevel QuillLogLevelToLogLevel(const quill::LogLevel p_level) + { + switch (p_level) + { + case quill::LogLevel::Debug: + return Flat::LogLevel::Debug; + case quill::LogLevel::TraceL3: + return Flat::LogLevel::Trace; + case quill::LogLevel::Info: + return Flat::LogLevel::Info; + case quill::LogLevel::Warning: + return Flat::LogLevel::Warn; + case quill::LogLevel::Error: + return Flat::LogLevel::Error; + case quill::LogLevel::Critical: + return Flat::LogLevel::Critical; + default: + break; + } + + return Flat::LogLevel::Trace; + } +}; + +/****************************************************************************************/ + +TEST_F(LogFixture, RegisterLogger_ShouldRegisterTheLogger) +{ + const quill::Logger* logger = Singleton::GetInstance().RegisterLogger(UTILSTESTS_LOGGER); + EXPECT_TRUE(logger); + EXPECT_EQ(logger, Singleton::GetInstance().GetLogger(UTILSTESTS_LOGGER)); + EXPECT_EQ(logger->get_logger_name(), UTILSTESTS_LOGGER.m_name); + EXPECT_EQ(QuillLogLevelToLogLevel(logger->get_log_level()), UTILSTESTS_LOGGER.m_level); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, UnregisterLogger_ShouldUnregisterTheLogger) +{ + std::ignore = Singleton::GetInstance().RegisterLogger(UTILSTESTS_LOGGER); + Singleton::GetInstance().UnregisterLogger(UTILSTESTS_LOGGER); + + EXPECT_EQ(Singleton::GetInstance().GetLogger(UTILSTESTS_LOGGER), nullptr); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, GetLogger_ShouldReturnNullptrIfTheLoggerDoesNotExist) +{ + EXPECT_FALSE(Singleton::GetInstance().GetLogger(UTILSTESTS_LOGGER)); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, GetLoger_ShouldReturnTheLoggerIfItExists) +{ + [[maybe_unused]] + const quill::Logger* logger = Singleton::GetInstance().RegisterLogger(UTILSTESTS_LOGGER); + EXPECT_TRUE(Singleton::GetInstance().GetLogger(UTILSTESTS_LOGGER)); +} + +/****************************************************************************************/ + +TEST_F(LogFixture, ChangeLoggerLogLevel_ShouldChangeTheLoggerLogLevel) +{ + const Flat::LogLevel previous = UTILSTESTS_LOGGER.m_level; + + const quill::Logger* logger = Singleton::GetInstance().RegisterLogger(UTILSTESTS_LOGGER); + + Singleton::GetInstance().ChangeLoggerLogLevel(UTILSTESTS_LOGGER, Flat::LogLevel::Critical); + EXPECT_EQ(QuillLogLevelToLogLevel(logger->get_log_level()), Flat::LogLevel::Critical); + Singleton::GetInstance().ChangeLoggerLogLevel(UTILSTESTS_LOGGER, previous); + EXPECT_EQ(QuillLogLevelToLogLevel(logger->get_log_level()), previous); +} +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Tests/Utils/Log/LogMacros.cpp b/Bigfoot/Tests/Utils/Log/LogMacros.cpp new file mode 100644 index 0000000..50f5640 --- /dev/null +++ b/Bigfoot/Tests/Utils/Log/LogMacros.cpp @@ -0,0 +1,86 @@ +/********************************************************************* + * \file LogMacros.cpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#include + +#include + +#include + +#ifdef BIGFOOT_NOT_OPTIMIZED + +#include +#include + +#include +#include +#include + +namespace Bigfoot +{ +class LogMacrosFixture: public ::testing::Test +{ + protected: + static std::string ReadFileSinkContent() + { + Singleton::GetInstance().Flush(); + + std::ifstream file {std::string {Log::FILE_SINK_PATH.data()}}; + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } +}; + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogDebug) +{ + BIGFOOT_LOG_DEBUG(UTILSTESTS_LOGGER, "Hello Debug"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Debug")); +} + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogTrace) +{ + BIGFOOT_LOG_TRACE(UTILSTESTS_LOGGER, "Hello Trace"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Trace")); +} + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogInfo) +{ + BIGFOOT_LOG_INFO(UTILSTESTS_LOGGER, "Hello Info"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Info")); +} + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogWarn) +{ + BIGFOOT_LOG_WARN(UTILSTESTS_LOGGER, "Hello Warn"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Warn")); +} + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogError) +{ + BIGFOOT_LOG_ERROR(UTILSTESTS_LOGGER, "Hello Error"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Error")); +} + +/****************************************************************************************/ + +TEST_F(LogMacrosFixture, LogFatal) +{ + BIGFOOT_LOG_FATAL(UTILSTESTS_LOGGER, "Hello Fatal"); + EXPECT_THAT(ReadFileSinkContent(), ::testing::HasSubstr("Hello Fatal")); +} +} // namespace Bigfoot +#endif diff --git a/Bigfoot/Tests/Utils/Singleton.cpp b/Bigfoot/Tests/Utils/Singleton.cpp index 4f089b3..98e4839 100644 --- a/Bigfoot/Tests/Utils/Singleton.cpp +++ b/Bigfoot/Tests/Utils/Singleton.cpp @@ -42,7 +42,7 @@ class SingletonTest } [[nodiscard]] - std::uint32_t Data() const + std::uint32_t GetData() const { return m_data; } @@ -84,19 +84,19 @@ TEST_F(SingletonFixture, ConstructorAndDestructorShouldBeCalled) { ::testing::InSequence seq; - EXPECT_CALL(*m_mock, Construct()); - EXPECT_CALL(*m_mock, Destruct()); + EXPECT_CALL(*m_mock, Construct()).Times(1); + EXPECT_CALL(*m_mock, Destruct()).Times(1); Singleton::Lifetime singleton {42}; } /****************************************************************************************/ -TEST_F(SingletonFixture, Instance_ShouldReturnTheInstance) +TEST_F(SingletonFixture, GetInstance_ShouldReturnTheInstance) { Singleton::Lifetime singleton {42}; - EXPECT_EQ(Singleton::Instance().Data(), 42); + EXPECT_EQ(Singleton::GetInstance().GetData(), 42); } } // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/TargetMacros.cpp b/Bigfoot/Tests/Utils/TargetMacros.cpp index 72f1aae..31975ed 100644 --- a/Bigfoot/Tests/Utils/TargetMacros.cpp +++ b/Bigfoot/Tests/Utils/TargetMacros.cpp @@ -23,4 +23,13 @@ TEST_F(TargetMacrosFixture, OptimizedOrNot) BIGFOOT_OPTIMIZED_ONLY(EXPECT_EQ(value, 1);) } +/****************************************************************************************/ + +TEST_F(TargetMacrosFixture, GameOrToolmode) +{ + constexpr std::uint32_t value = BIGFOOT_GAME_OR_TOOLMODE(1, 2); + BIGFOOT_GAME_ONLY(EXPECT_EQ(value, 1);) + BIGFOOT_TOOLMODE_ONLY(EXPECT_EQ(value, 2);) +} + } // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/UtilsTests/main.cpp b/Bigfoot/Tests/Utils/UtilsTests/main.cpp new file mode 100644 index 0000000..0a50313 --- /dev/null +++ b/Bigfoot/Tests/Utils/UtilsTests/main.cpp @@ -0,0 +1,21 @@ +/********************************************************************* + * \file main.cpp + * + * \author Romain BOULLARD + * \date May 2026 + *********************************************************************/ +#include +#include + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + + GTEST_FLAG_SET(death_test_style, "threadsafe"); + + BIGFOOT_NOT_OPTIMIZED_ONLY(::Bigfoot::Singleton<::Bigfoot::Log>::Lifetime m_lifetime); + + return RUN_ALL_TESTS(); +} diff --git a/Bigfoot/Tests/Utils/Version.cpp b/Bigfoot/Tests/Utils/Version.cpp index d2e6912..34feb36 100644 --- a/Bigfoot/Tests/Utils/Version.cpp +++ b/Bigfoot/Tests/Utils/Version.cpp @@ -6,6 +6,8 @@ *********************************************************************/ #include +#include + #include namespace Bigfoot @@ -19,42 +21,42 @@ class VersionFixture: public ::testing::Test /****************************************************************************************/ -TEST_F(VersionFixture, string) +TEST_F(VersionFixture, ToString) { - EXPECT_STREQ(static_cast(m_detailed).data(), "1.2.3"); - EXPECT_STREQ(static_cast(m_combined).data(), "1.2.3"); + EXPECT_STREQ(m_detailed.ToString().data(), "1.2.3"); + EXPECT_STREQ(m_combined.ToString().data(), "1.2.3"); } /****************************************************************************************/ -TEST_F(VersionFixture, uint32_t) +TEST_F(VersionFixture, Raw) { - EXPECT_EQ(static_cast(m_detailed), (1 << 16) | (2 << 8) | 3); - EXPECT_EQ(static_cast(m_combined), (1 << 16) | (2 << 8) | 3); + EXPECT_EQ(m_detailed.Raw(), (1 << 16) | (2 << 8) | 3); + EXPECT_EQ(m_combined.Raw(), (1 << 16) | (2 << 8) | 3); } /****************************************************************************************/ -TEST_F(VersionFixture, Major_ShouldBeEqualToTheMajorPartOfTheVersion) +TEST_F(VersionFixture, GetMajor_ShouldBeEqualToTheMajorPartOfTheVersion) { - EXPECT_EQ(m_detailed.Major(), 1); - EXPECT_EQ(m_combined.Major(), 1); + EXPECT_EQ(m_detailed.GetMajor(), 1); + EXPECT_EQ(m_combined.GetMajor(), 1); } /****************************************************************************************/ -TEST_F(VersionFixture, Minor_ShouldBeEqualToTheMinorPartOfTheVersion) +TEST_F(VersionFixture, GetMinor_ShouldBeEqualToTheMinorPartOfTheVersion) { - EXPECT_EQ(m_detailed.Minor(), 2); - EXPECT_EQ(m_combined.Minor(), 2); + EXPECT_EQ(m_detailed.GetMinor(), 2); + EXPECT_EQ(m_combined.GetMinor(), 2); } /****************************************************************************************/ -TEST_F(VersionFixture, Patch_ShouldBeEqualToThePatchPartOfTheVersion) +TEST_F(VersionFixture, GetPatch_ShouldBeEqualToThePatchPartOfTheVersion) { - EXPECT_EQ(m_detailed.Patch(), 3); - EXPECT_EQ(m_combined.Patch(), 3); + EXPECT_EQ(m_detailed.GetPatch(), 3); + EXPECT_EQ(m_combined.GetPatch(), 3); } /****************************************************************************************/ @@ -70,4 +72,12 @@ TEST_F(VersionFixture, Comparisons) EXPECT_EQ(other, other); EXPECT_NE(other, m_detailed); } + +/****************************************************************************************/ + +TEST_F(VersionFixture, FormatsVersion) +{ + EXPECT_STREQ(Format("{}", m_detailed).c_str(), "1.2.3"); + EXPECT_STREQ(Format("{}", m_combined).c_str(), "1.2.3"); +} } // namespace Bigfoot diff --git a/CMake/CustomTargets.cmake b/CMake/CustomTargets.cmake new file mode 100644 index 0000000..66807c4 --- /dev/null +++ b/CMake/CustomTargets.cmake @@ -0,0 +1,22 @@ +add_library(BigfootCompileAndLinkFlags INTERFACE) + +target_compile_options(BigfootCompileAndLinkFlags INTERFACE + $<$:/W4 /WX> + $<$:-Wall -Wextra -Wpedantic -Werror> + $<$,$>:-fprofile-instr-generate -fcoverage-mapping -fprofile-continuous> +) + +target_link_options(BigfootCompileAndLinkFlags INTERFACE + $<$,$>:-fprofile-instr-generate -fprofile-continuous> +) + +target_compile_definitions(BigfootCompileAndLinkFlags INTERFACE + $<$:NOMINMAX> + $<$:WIN32_LEAN_AND_MEAN> + $<$:BIGFOOT_WINDOWS> + $<$:BIGFOOT_LINUX> + $<$:BIGFOOT_OPTIMIZED> + $<$:BIGFOOT_NOT_OPTIMIZED> + $,BIGFOOT_TOOLMODE,BIGFOOT_GAME> + $<$:BIGFOOT_BUILD_TESTS> + $<$:BIGFOOT_BUILD_BENCHMARKS>) \ No newline at end of file diff --git a/CMake/FindDependencies.cmake b/CMake/FindDependencies.cmake index b56d7f0..e8f3d6e 100644 --- a/CMake/FindDependencies.cmake +++ b/CMake/FindDependencies.cmake @@ -6,14 +6,23 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") endif() find_package(EASTL REQUIRED) +find_package(EAStdC REQUIRED) find_package(unordered_dense REQUIRED) -find_package(mimalloc REQUIRED) +if(${ASAN}) + find_package(mimalloc-asan REQUIRED) +else() + find_package(mimalloc REQUIRED) +endif() find_package(stduuid REQUIRED) find_package(SQLite3 REQUIRED) -find_package(CLI11 REQUIRED) find_package(rapidhash REQUIRED) find_package(effolkronium_random REQUIRED) +find_package(fmt REQUIRED) find_package(flatbuffers CONFIG REQUIRED) +find_package(lyra REQUIRED) +find_package(glm REQUIRED) +find_package(lodepng REQUIRED) +find_package(imgui REQUIRED) if(TRACY) find_package(Tracy REQUIRED) @@ -21,44 +30,20 @@ endif() if(${IS_MULTI_CONFIG}) find_package(quill REQUIRED) - find_package(cpptrace REQUIRED) elseif(${CMAKE_BUILD_TYPE} STREQUAL "Debug" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") find_package(quill REQUIRED) - find_package(cpptrace REQUIRED) endif() - -find_package(glm REQUIRED) -find_package(lodepng REQUIRED) -find_package(imgui REQUIRED) - if(VULKAN) find_package(VulkanHeaders REQUIRED) - if(${IS_MULTI_CONFIG}) - find_package(vulkan-validationlayers REQUIRED) - elseif(${CMAKE_BUILD_TYPE} STREQUAL "Debug" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") - find_package(vulkan-validationlayers REQUIRED) - endif() find_package(vulkan-memory-allocator REQUIRED) endif() -if(BUILD_BENCHMARKS OR BUILD_TESTS OR SAMPLE_APP) - find_package(glfw3 REQUIRED) -endif() - if(BUILD_TESTS) find_package(GTest REQUIRED) find_package(pixelmatch-cpp17 REQUIRED) endif() -if(BUILD_TOOLS) - find_package(spirv-cross REQUIRED) - find_package(shaderc REQUIRED) - find_package(assimp REQUIRED) - find_package(meshoptimizer REQUIRED) - find_package(libsquish REQUIRED) -endif() - if(BUILD_BENCHMARKS) find_package(benchmark REQUIRED) endif() \ No newline at end of file diff --git a/CMake/Package.cmake b/CMake/Package.cmake index ed86db9..ea6baac 100644 --- a/CMake/Package.cmake +++ b/CMake/Package.cmake @@ -1,97 +1,310 @@ function(bigfoot_create_package_lib PackagePublicDependencies PackagePrivateDependencies PackageBigfootPublicDependencies PackageBigfootPrivateDependencies ParentFolder) add_library(${PROJECT_NAME} STATIC) - set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) - target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Include) + target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) - bigfoot_compile_flatbuffers("${PackageBigfootPublicDependencies}") + target_link_libraries(${PROJECT_NAME} + PRIVATE + BigfootCompileAndLinkFlags + PUBLIC + ${PackagePublicDependencies} + $ - # collect sources (reconfigure when files are added/removed) - file(GLOB_RECURSE _BF_SOURCES - CONFIGURE_DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/*.h - ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs - ${CMAKE_CURRENT_SOURCE_DIR}/*.sql + PRIVATE + ${PackagePrivateDependencies} + $) + + bigfoot_compile_flatbuffers() + + 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 + ) + + file(GLOB_RECURSE _OTHERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs + ${CMAKE_CURRENT_SOURCE_DIR}/*.sql ) target_sources(${PROJECT_NAME} PRIVATE - ${_BF_SOURCES} + ${_SOURCES} + ${_OTHERS} + PUBLIC + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${_HEADERS} ) - target_compile_options(${PROJECT_NAME} PRIVATE ${BIGFOOT_CXX_FLAGS}) - - target_link_libraries(${PROJECT_NAME} PUBLIC unordered_dense::unordered_dense EASTL::EASTL flatbuffers::flatbuffers rapidhash::rapidhash) - target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_DL_LIBS}) - - target_link_libraries(${PROJECT_NAME} PUBLIC ${PackagePublicDependencies}) - target_link_libraries(${PROJECT_NAME} PRIVATE ${PackagePrivateDependencies}) - target_link_libraries(${PROJECT_NAME} PUBLIC $) - target_link_libraries(${PROJECT_NAME} PRIVATE $) - - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${_BF_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${_SOURCES} ${_HEADERS} ${_OTHERS}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Bigfoot/${ParentFolder}) endfunction() -function(bigfoot_create_package_tests ParentFolder BigfootDependencies) +function(bigfoot_create_package_tests PackageDependencies PackageBigfootDependencies ParentFolder) add_executable(${PROJECT_NAME}) - set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) - bigfoot_compile_flatbuffers("${BigfootDependencies}") + target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) - file(GLOB_RECURSE _BF_TEST_SOURCES - CONFIGURE_DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/*.h - ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs + target_link_libraries(${PROJECT_NAME} + PRIVATE + BigfootCompileAndLinkFlags + ${PackageDependencies} + $ + $ + gtest::gtest) + + bigfoot_compile_flatbuffers() + + 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 + ) + + file(GLOB_RECURSE _OTHERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs + ${CMAKE_CURRENT_SOURCE_DIR}/*.sql ) target_sources(${PROJECT_NAME} PRIVATE - ${_BF_TEST_SOURCES} + ${_SOURCES} + ${_OTHERS} + PUBLIC + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${_HEADERS} ) - target_compile_options(${PROJECT_NAME} PRIVATE ${BIGFOOT_CXX_FLAGS}) - target_link_options(${PROJECT_NAME} PRIVATE ${BIGFOOT_EXE_LINK_FLAGS}) - - target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Include) - - target_link_libraries(${PROJECT_NAME} PRIVATE $) - target_link_libraries(${PROJECT_NAME} PRIVATE gtest::gtest) - 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 ${_BF_TEST_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src/ FILES ${_SOURCES} ${_HEADERS} ${_OTHERS}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests/Bigfoot/${ParentFolder}) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$") + + ##################ASAN SETUP################### + + if(${ASAN}) + if(MSVC) + get_filename_component(MSVC_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(ASAN_DLL "${MSVC_BIN_DIR}/clang_rt.asan_dynamic-x86_64.dll") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + DEPENDS "${ASAN_DLL}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ASAN_DLL}" "$" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + COMMENT "Copying ASan DLL for ${PROJECT_NAME}" + ) + + add_custom_target(${PROJECT_NAME}Asan + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + ) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Asan) + set_target_properties(${PROJECT_NAME}Asan PROPERTIES FOLDER UtilityTargets/Tests/Bigfoot/${ParentFolder}) + endif() + endif() ##################COPY FIXTURE FOLDER################### - if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) endif() - # Track all fixture files file(GLOB_RECURSE FIXTURE_FILES - CONFIGURE_DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/Fixture/* + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/Fixture/* + ) + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + DEPENDS ${FIXTURE_FILES} + COMMAND ${CMAKE_COMMAND} -E remove_directory $/Fixture + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Fixture $/Fixture + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + COMMENT "Copying Fixture folder for ${PROJECT_NAME}" ) add_custom_target(${PROJECT_NAME}Fixture - COMMAND ${CMAKE_COMMAND} -E remove_directory $/Fixture - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Fixture $/Fixture - DEPENDS ${FIXTURE_FILES} - COMMENT "Copying Fixture folder for ${PROJECT_NAME}" + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" ) add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Fixture) - set_target_properties(${PROJECT_NAME}Fixture PROPERTIES FOLDER UtilityTargets/Tests/Bigfoot/${ParentFolder}) +endfunction() + +function(bigfoot_create_package_tests_helper_lib PackagePublicDependencies PackagePrivateDependencies PackageBigfootPublicDependencies PackageBigfootPrivateDependencies ParentFolder) + add_library(${PROJECT_NAME} STATIC) + + target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + + target_link_libraries(${PROJECT_NAME} + PRIVATE + BigfootCompileAndLinkFlags + PUBLIC + ${PackagePublicDependencies} + $ + + PRIVATE + ${PackagePrivateDependencies} + $) + + bigfoot_compile_flatbuffers() + + 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 + ) + + file(GLOB_RECURSE _OTHERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs + ${CMAKE_CURRENT_SOURCE_DIR}/*.sql + ) + + target_sources(${PROJECT_NAME} + PRIVATE + ${_SOURCES} + ${_OTHERS} + PUBLIC + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${_HEADERS} + ) + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src FILES ${_SOURCES} ${_HEADERS} ${_OTHERS}) + + set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Tests/Bigfoot/${ParentFolder}) +endfunction() + +function(bigfoot_create_package_benchmarks ParentFolder) + add_executable(${PROJECT_NAME}) + + target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + + target_link_libraries(${PROJECT_NAME} + PRIVATE + BigfootCompileAndLinkFlags + $ + benchmark::benchmark_main) + + bigfoot_compile_flatbuffers() + + 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 + ) + + file(GLOB_RECURSE _OTHERS + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/*.fbs + ${CMAKE_CURRENT_SOURCE_DIR}/*.sql + ) + + target_sources(${PROJECT_NAME} + PRIVATE + ${_SOURCES} + ${_OTHERS} + PUBLIC + FILE_SET HEADERS + BASE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/Include + FILES + ${_HEADERS} + ) + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX Src/ FILES ${_SOURCES} ${_HEADERS} ${_OTHERS}) + + set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER Benchmarks/Bigfoot/${ParentFolder}) + set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "$") + + ##################ASAN SETUP################### + + if(${ASAN}) + if(MSVC) + get_filename_component(MSVC_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(ASAN_DLL "${MSVC_BIN_DIR}/clang_rt.asan_dynamic-x86_64.dll") + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + DEPENDS "${ASAN_DLL}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ASAN_DLL}" "$" + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + COMMENT "Copying ASan DLL for ${PROJECT_NAME}" + ) + + add_custom_target(${PROJECT_NAME}Asan + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-asan.timestamp" + ) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Asan) + set_target_properties(${PROJECT_NAME}Asan PROPERTIES FOLDER UtilityTargets/Benchmarks/Bigfoot/${ParentFolder}) + endif() + endif() + + ##################COPY FIXTURE FOLDER################### + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Fixture) + endif() + + file(GLOB_RECURSE FIXTURE_FILES + CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/Fixture/* + ) + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + DEPENDS ${FIXTURE_FILES} + COMMAND ${CMAKE_COMMAND} -E remove_directory $/Fixture + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/Fixture $/Fixture + COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + COMMENT "Copying Fixture folder for ${PROJECT_NAME}" + ) + + add_custom_target(${PROJECT_NAME}Fixture + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-fixture.timestamp" + ) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}Fixture) + set_target_properties(${PROJECT_NAME}Fixture PROPERTIES FOLDER UtilityTargets/Benchmarks/Bigfoot/${ParentFolder}) endfunction() \ No newline at end of file diff --git a/CMake/Utils.cmake b/CMake/Utils.cmake index 9841ed8..ce0d0df 100644 --- a/CMake/Utils.cmake +++ b/CMake/Utils.cmake @@ -2,13 +2,13 @@ function(bigfoot_create_logger) set(LOGGER_FILENAME ${PROJECT_NAME}Logger) string(TOUPPER ${PROJECT_NAME}_Logger LOGGER_NAME) string(TOUPPER ${LOGGER_FILENAME} LOGGER_FILENAME_UPPER) - configure_file( ${CMAKE_SOURCE_DIR}/Bigfoot/Sources/System/Include/System/Log/TargetLogger_generated.hpp.in + configure_file( ${CMAKE_SOURCE_DIR}/Bigfoot/Sources/Utils/Include/Utils/Log/TargetLogger_generated.hpp.in ${CMAKE_CURRENT_SOURCE_DIR}/Include/${PROJECT_NAME}/${LOGGER_FILENAME}_generated.hpp @ONLY) endfunction() function(bigfoot_create_bigfile ParentFolder) - set(OUTPUT_PATH_BIGFILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${PROJECT_NAME}-bigfile.db") + set(OUTPUT_PATH_BIGFILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${PROJECT_NAME}.db") get_filename_component(OUTPUT_PATH_BIGFILE_ABSOLUTE ${OUTPUT_PATH_BIGFILE} ABSOLUTE) get_filename_component(OUTPUT_PATH_BIGFILE_DIRECTORY ${OUTPUT_PATH_BIGFILE_ABSOLUTE} DIRECTORY) @@ -20,35 +20,81 @@ function(bigfoot_create_bigfile ParentFolder) COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT_PATH_BIGFILE_ABSOLUTE}.bftimestamp COMMENT "Creating Bigfile ${OUTPUT_PATH_BIGFILE_ABSOLUTE}" ) - list(APPEND BIGFILE_SOURCES ${OUTPUT_PATH_BIGFILE_ABSOLUTE}.bftimestamp) - add_custom_target(${PROJECT_NAME}BigFile ALL DEPENDS ${BIGFILE_SOURCES}) + add_custom_target(${PROJECT_NAME}BigFile ALL + DEPENDS ${OUTPUT_PATH_BIGFILE_ABSOLUTE}.bftimestamp + ) + set_target_properties(${PROJECT_NAME}BigFile PROPERTIES FOLDER UtilityTargets/${ParentFolder}) - add_dependencies(${PROJECT_NAME}BigFile ${PROJECT_NAME}) - - target_sources(${PROJECT_NAME}BigFile PRIVATE ${BIGFILE_SOURCES}) + + add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}BigFile) string(TOUPPER ${PROJECT_NAME} BIGFILE_NAME) - set(BIGFILE_LOCATION "./${PROJECT_NAME}-bigfile.db") - configure_file( ${CMAKE_SOURCE_DIR}/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/Include/${PROJECT_NAME}/BigFileInfo_generated.hpp - @ONLY) + set(BIGFILE_LOCATION "./${PROJECT_NAME}.db") + configure_file( + ${CMAKE_SOURCE_DIR}/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFileInfo_generated.hpp.in + ${CMAKE_CURRENT_SOURCE_DIR}/Include/${PROJECT_NAME}/BigFileInfo_generated.hpp + @ONLY + ) endfunction() -function(bigfoot_compile_flatbuffers BigfootDependencies) - set(IncludeFolders "") +function(bigfoot_create_sasquatch_info) + string(TOUPPER ${PROJECT_NAME} SASQUATCH_NAME) + set(SASQUATCH_LOCATION "./${PROJECT_NAME}.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() - get_target_property(CurrentTargetDependencyInclude ${PROJECT_NAME} INCLUDE_DIRECTORIES) - if(CurrentTargetDependencyInclude) - list(APPEND IncludeFolders ${CurrentTargetDependencyInclude}) +function(_bigfoot_collect_includes_recursive Target UseAllLinks) + get_property(_Visited GLOBAL PROPERTY _BIGFOOT_VISITED_TARGETS) + if("${Target}" IN_LIST _Visited) + return() endif() - - foreach(Dependency IN LISTS BigfootDependencies) - get_target_property(DependencyInclude ${Dependency} INCLUDE_DIRECTORIES) - if(DependencyInclude) - list(APPEND IncludeFolders ${DependencyInclude}) + set_property(GLOBAL APPEND PROPERTY _BIGFOOT_VISITED_TARGETS "${Target}") + + get_target_property(_IncDirs "${Target}" INTERFACE_INCLUDE_DIRECTORIES) + if(_IncDirs) + foreach(_Dir IN LISTS _IncDirs) + if(_Dir MATCHES "^\\$$") + set_property(GLOBAL APPEND PROPERTY _BIGFOOT_INCLUDE_DIRS "${CMAKE_MATCH_1}") + elseif(NOT _Dir MATCHES "^\\$<") + set_property(GLOBAL APPEND PROPERTY _BIGFOOT_INCLUDE_DIRS "${_Dir}") + endif() + endforeach() + endif() + + if(UseAllLinks) + get_target_property(_Libs "${Target}" LINK_LIBRARIES) + else() + get_target_property(_Libs "${Target}" INTERFACE_LINK_LIBRARIES) + endif() + if(NOT _Libs) + return() + endif() + foreach(_Lib IN LISTS _Libs) + if(_Lib MATCHES "^\\$$") + foreach(_Inner IN LISTS CMAKE_MATCH_1) + if(TARGET "${_Inner}") + _bigfoot_collect_includes_recursive("${_Inner}" FALSE) + endif() + endforeach() + elseif(NOT _Lib MATCHES "^\\$<" AND TARGET "${_Lib}") + _bigfoot_collect_includes_recursive("${_Lib}" FALSE) endif() endforeach() +endfunction() + +function(bigfoot_compile_flatbuffers) + set_property(GLOBAL PROPERTY _BIGFOOT_VISITED_TARGETS "") + set_property(GLOBAL PROPERTY _BIGFOOT_INCLUDE_DIRS "") + _bigfoot_collect_includes_recursive(${PROJECT_NAME} TRUE) + + get_property(_CollectedDirs GLOBAL PROPERTY _BIGFOOT_INCLUDE_DIRS) + set(IncludeFolders "${CMAKE_CURRENT_SOURCE_DIR}/Include" ${_CollectedDirs}) + list(REMOVE_DUPLICATES IncludeFolders) set(IncludeFlags "") foreach(folder IN LISTS IncludeFolders) @@ -58,7 +104,6 @@ function(bigfoot_compile_flatbuffers BigfootDependencies) file(GLOB_RECURSE SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/Include/*.fbs") foreach(SOURCE_FILE IN LISTS SOURCES) get_filename_component(SOURCE_DIRECTORY ${SOURCE_FILE} DIRECTORY) - set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${SOURCE_FILE}) execute_process( @@ -67,9 +112,9 @@ function(bigfoot_compile_flatbuffers BigfootDependencies) ${IncludeFlags} --keep-prefix --filename-ext "hpp" - --reflect-types --cpp-std c++17 --reflect-names + --reflect-types --gen-name-strings --gen-object-api --cpp-str-flex-ctor @@ -77,69 +122,19 @@ function(bigfoot_compile_flatbuffers BigfootDependencies) --force-empty --cpp-ptr-type "eastl::unique_ptr" --cpp-str-type "eastl::string" + --cpp-vector-type "eastl::vector" --cpp-include "EASTL/unique_ptr.h" --cpp-include "EASTL/string.h" + --cpp-include "EASTL/vector.h" -o "${SOURCE_DIRECTORY}" - "${SOURCE_FILE}" + --schema "${SOURCE_FILE}" ) endforeach() endfunction() -function(bigfoot_setup_dependencies ParentFolder) - set(CONAN_DEPLOYER_DIR "${CMAKE_SOURCE_DIR}/build/full_deploy/host") - - if(EXISTS ${CONAN_DEPLOYER_DIR}) - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - file(GLOB_RECURSE SHARED_BINARIES ${CONAN_DEPLOYER_DIR}/*mimalloc*.dll) - elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - file(GLOB_RECURSE SHARED_BINARIES ${CONAN_DEPLOYER_DIR}/*mimalloc*.so*) - endif() - - if(${IS_MULTI_CONFIG}) - foreach(CONFIG ${CMAKE_CONFIGURATION_TYPES}) - foreach(file ${SHARED_BINARIES}) - if(file MATCHES "/${CONFIG}/") - list(APPEND SHARED_BINARIES_${CONFIG} ${file}) - endif() - endforeach() - endforeach() - - add_custom_target(${PROJECT_NAME}CopySharedBinaries - ALL DEPENDS SHARED_BINARIES - COMMAND ${CMAKE_COMMAND} -E make_directory $ - COMMAND ${CMAKE_COMMAND} -E $,copy_if_different,true> ${SHARED_BINARIES_Debug} $ - COMMAND ${CMAKE_COMMAND} -E $,copy_if_different,true> ${SHARED_BINARIES_Release} $ - COMMAND ${CMAKE_COMMAND} -E $,copy_if_different,true> ${SHARED_BINARIES_RelWithDebInfo} $ - COMMENT "Copy shared binaries for ${PROJECT_NAME}" - ) - add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}CopySharedBinaries) - set_target_properties(${PROJECT_NAME}CopySharedBinaries PROPERTIES FOLDER UtilityTargets/${ParentFolder}) - - else() - foreach(file ${SHARED_BINARIES}) - if(file MATCHES "/${CMAKE_BUILD_TYPE}/") - list(APPEND SHARED_BINARIES_${CMAKE_BUILD_TYPE} ${file}) - endif() - endforeach() - - add_custom_target(${PROJECT_NAME}CopySharedBinaries ALL - DEPENDS ${SHARED_BINARIES_${CMAKE_BUILD_TYPE}} - COMMAND ${CMAKE_COMMAND} -E make_directory $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SHARED_BINARIES_${CMAKE_BUILD_TYPE}} $ - COMMENT "Copy shared binaries for ${PROJECT_NAME}" - ) - add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}CopySharedBinaries) - set_target_properties(${PROJECT_NAME}CopySharedBinaries PROPERTIES FOLDER UtilityTargets/${ParentFolder}) - endif() +macro(bigfoot_remove_default_exception_flags) + if(MSVC) + string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REPLACE "/EHs" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") endif() - - if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - add_custom_target(${PROJECT_NAME}PatchMinject ALL - COMMAND ${MINJECT_EXECUTABLE} -i -f $ - COMMENT "Patching ${PROJECT_NAME} to ensure mimalloc dynamic override" - ) - add_dependencies(${PROJECT_NAME}PatchMinject ${PROJECT_NAME}) - set_target_properties(${PROJECT_NAME}PatchMinject PROPERTIES FOLDER "UtilityTargets/${ParentFolder}") - endif() - -endfunction() \ No newline at end of file +endmacro() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 665a2ff..3ac58d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,21 @@ -cmake_minimum_required(VERSION 3.24) +cmake_minimum_required(VERSION 3.26) -# CMake sets this flag by default, we don't use exception in bigfoot, we can remove it -string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +# Probe project to load the toolchain file (which sets TOOLMODE) before the +# real project() call decides between the tool and game solution names. +project(BigfootProbe LANGUAGES NONE) -project(Bigfoot VERSION 0.1.0 +if(NOT DEFINED TOOLMODE OR TOOLMODE) + set(BIGFOOT_PROJECT_NAME BigfootTool) +else() + set(BIGFOOT_PROJECT_NAME BigfootGame) +endif() + +project(${BIGFOOT_PROJECT_NAME} VERSION 0.1.0 DESCRIPTION "The Bigfoot engine" LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) @@ -16,13 +23,16 @@ get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo;Debug" CACHE STRING "" FORCE) option(BUILD_TESTS OFF) -option(TRACY ON) -option(BUILD_TOOLS ON) -option(VULKAN ON) option(BUILD_BENCHMARKS OFF) +option(ASAN OFF) +option(COVERAGE OFF) +option(TRACY ON) +option(VULKAN ON) +option(TOOLMODE ON) set(AUTO_GENERATED_COMMENT "// AUTO-GENERATED DO NOT TOUCH") +include(${CMAKE_SOURCE_DIR}/CMake/CustomTargets.cmake) include(${CMAKE_SOURCE_DIR}/CMake/FindDependencies.cmake) include(${CMAKE_SOURCE_DIR}/CMake/Utils.cmake) include(${CMAKE_SOURCE_DIR}/CMake/Package.cmake) @@ -30,18 +40,13 @@ include(${CMAKE_SOURCE_DIR}/CMake/Package.cmake) find_program(FLATBUFFERS_FLATC_EXECUTABLE NAMES flatc) find_program(SQLITE3_EXECUTABLE NAMES sqlite3) find_program(MINJECT_EXECUTABLE NAMES minject) +find_program(BIN2CPP_EXECUTABLE NAMES Bin2CPP) + +bigfoot_remove_default_exception_flags() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_OPTIMIZE_DEPENDENCIES 1) -add_compile_definitions( - $<$:NOMINMAX> - $<$:WIN32_LEAN_AND_MEAN> - $<$:BIGFOOT_WINDOWS> - $<$:BIGFOOT_LINUX> - $<$:BIGFOOT_OPTIMIZED> - $<$:BIGFOOT_NOT_OPTIMIZED>) - if(BUILD_TESTS) enable_testing() endif() @@ -56,6 +61,9 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bigfoot/Sources) if(${BUILD_TESTS}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bigfoot/Tests) endif() +if(${BUILD_BENCHMARKS}) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Bigfoot/Benchmarks) +endif() add_custom_target(NatVis SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Vendor/NatVis/EASTL/EASTL.natvis diff --git a/ConanProfiles/Toolchains/ipo.cmake b/ConanProfiles/Toolchains/ipo.cmake new file mode 100644 index 0000000..7e83475 --- /dev/null +++ b/ConanProfiles/Toolchains/ipo.cmake @@ -0,0 +1,5 @@ +include_guard() + +set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) + +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE) \ No newline at end of file diff --git a/ConanProfiles/Toolchains/ipo.ini b/ConanProfiles/Toolchains/ipo.ini new file mode 100644 index 0000000..bf155da --- /dev/null +++ b/ConanProfiles/Toolchains/ipo.ini @@ -0,0 +1,2 @@ +[built-in options] +b_lto = true \ No newline at end of file diff --git a/ConanProfiles/Toolchains/mold.cmake b/ConanProfiles/Toolchains/mold.cmake new file mode 100644 index 0000000..8d48ddf --- /dev/null +++ b/ConanProfiles/Toolchains/mold.cmake @@ -0,0 +1,9 @@ +include_guard() + +find_program(MOLD_PROGRAM mold) +if (MOLD_PROGRAM) + set(CMAKE_LINKER_TYPE MOLD) + message(STATUS "mold linker found: ${MOLD_PROGRAM}") +else () + message(WARNING "mold linker not found") +endif () \ No newline at end of file diff --git a/ConanProfiles/build/clang b/ConanProfiles/build/clang new file mode 100644 index 0000000..ff4b135 --- /dev/null +++ b/ConanProfiles/build/clang @@ -0,0 +1,24 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} + +tools.info.package_id:confs=["tools.build:cflags$", "tools.build:cxxflags$", "tools.build:defines$", "tools.build:sharedlinkflags$", "tools.build:exelinkflags$", "tools.build:compiler_executables$", "tools.cmake.cmaketoolchain:user_toolchain$", "tools.meson.mesontoolchain:extra_machine_files$"] + +[tool_requires] +!cmake/*: cmake/4.3.2 \ No newline at end of file diff --git a/ConanProfiles/build/msvc b/ConanProfiles/build/msvc new file mode 100644 index 0000000..7dde8e4 --- /dev/null +++ b/ConanProfiles/build/msvc @@ -0,0 +1,23 @@ +[settings] +os=Windows +arch=x86_64 +compiler=msvc +compiler.version=195 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope"] + +tools.env.virtualenv:powershell=powershell.exe + +tools.info.package_id:confs=["tools.build:cflags$", "tools.build:cxxflags$", "tools.build:defines$", "tools.build:sharedlinkflags$", "tools.build:exelinkflags$", "tools.build:compiler_executables$", "tools.cmake.cmaketoolchain:user_toolchain$", "tools.meson.mesontoolchain:extra_machine_files$"] + +[tool_requires] +!cmake/*: cmake/4.3.2 \ No newline at end of file diff --git a/ConanProfiles/host/Common/clang b/ConanProfiles/host/Common/clang new file mode 100644 index 0000000..33d5fbd --- /dev/null +++ b/ConanProfiles/host/Common/clang @@ -0,0 +1,24 @@ +[settings] +os=Linux +arch=x86_64 +compiler=clang +compiler.version=20 +compiler.libcxx=libstdc++11 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static + +[conf] +tools.system.package_manager:mode=install +tools.system.package_manager:sudo=True + +tools.build:compiler_executables={"c": "clang", "cpp": "clang++"} + +tools.cmake.cmaketoolchain:generator=Ninja + +tools.info.package_id:confs=["tools.build:cflags$", "tools.build:cxxflags$", "tools.build:defines$", "tools.build:sharedlinkflags$", "tools.build:exelinkflags$", "tools.build:compiler_executables$", "tools.cmake.cmaketoolchain:user_toolchain$", "tools.meson.mesontoolchain:extra_machine_files$"] + +[tool_requires] +!cmake/*: cmake/4.3.2 +!mold/*: mold/2.41.0@bigfootdev/main +!ninja/*: ninja/1.13.2 diff --git a/ConanProfiles/host/Common/msvc b/ConanProfiles/host/Common/msvc new file mode 100644 index 0000000..37f9366 --- /dev/null +++ b/ConanProfiles/host/Common/msvc @@ -0,0 +1,18 @@ +[settings] +os=Windows +arch=x86_64 +compiler=msvc +compiler.version=195 +compiler.cppstd=20 +compiler.cstd=17 +compiler.runtime=static + +[conf] +tools.build:defines=["_HAS_EXCEPTIONS=0"] + +tools.env.virtualenv:powershell=powershell.exe + +tools.info.package_id:confs=["tools.build:cflags$", "tools.build:cxxflags$", "tools.build:defines$", "tools.build:sharedlinkflags$", "tools.build:exelinkflags$", "tools.build:compiler_executables$", "tools.cmake.cmaketoolchain:user_toolchain$", "tools.meson.mesontoolchain:extra_machine_files$"] + +[tool_requires] +!cmake/*: cmake/4.3.2 diff --git a/ConanProfiles/host/clang b/ConanProfiles/host/clang new file mode 100644 index 0000000..6bf0597 --- /dev/null +++ b/ConanProfiles/host/clang @@ -0,0 +1,14 @@ +include(Common/clang) + +[settings] +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/mold.cmake +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.build:cxxflags=["-fno-exceptions", "-fno-rtti"] + +[options] +bigfoot/*:build_tests=True diff --git a/ConanProfiles/host/clang_asan b/ConanProfiles/host/clang_asan new file mode 100644 index 0000000..776b50f --- /dev/null +++ b/ConanProfiles/host/clang_asan @@ -0,0 +1,17 @@ +include(Common/clang) + +[settings] +build_type=Debug + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/mold.cmake + +tools.build:exelinkflags=["-fsanitize=address,undefined,leak"] +tools.build:sharedlinkflags=["-fsanitize=address,undefined,leak"] + +tools.build:cflags=["-fsanitize=address,undefined,leak", "-fno-sanitize-recover=all"] +tools.build:cxxflags=["-fno-exceptions", "-fno-rtti", "-fsanitize=address,undefined,leak", "-fno-sanitize-recover=all"] + +[options] +bigfoot/*:asan=True +bigfoot/*:build_tests=True diff --git a/ConanProfiles/host/clang_coverage b/ConanProfiles/host/clang_coverage new file mode 100644 index 0000000..5d3075c --- /dev/null +++ b/ConanProfiles/host/clang_coverage @@ -0,0 +1,13 @@ +include(Common/clang) + +[settings] +build_type=Debug + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/mold.cmake + +tools.build:cxxflags=["-fno-exceptions", "-fno-rtti"] + +[options] +bigfoot/*:build_tests=True +bigfoot/*:coverage=True diff --git a/ConanProfiles/host/msvc b/ConanProfiles/host/msvc new file mode 100644 index 0000000..2fc8f45 --- /dev/null +++ b/ConanProfiles/host/msvc @@ -0,0 +1,14 @@ +include(Common/msvc) + +[settings] +build_type=Release + +[conf] +tools.cmake.cmaketoolchain:user_toolchain+={{profile_dir}}/../Toolchains/ipo.cmake +tools.meson.mesontoolchain:extra_machine_files+={{profile_dir}}/../Toolchains/ipo.ini + +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:externConstexpr", "/Zc:lambda", "/Zc:throwingNew", "/volatile:iso", "/EHs-c-", "/GR-"] + +[options] +bigfoot/*:build_tests=True diff --git a/ConanProfiles/host/msvc_asan b/ConanProfiles/host/msvc_asan new file mode 100644 index 0000000..1d0f535 --- /dev/null +++ b/ConanProfiles/host/msvc_asan @@ -0,0 +1,12 @@ +include(Common/msvc) + +[settings] +build_type=Debug + +[conf] +tools.build:cflags=["/Zc:preprocessor", "/Zc:__STDC__", "/D_CRT_DECLARE_NONSTDC_NAMES=1", "/fsanitize=address"] +tools.build:cxxflags=["/Zc:preprocessor", "/permissive-", "/Zc:__cplusplus", "/Zc:enumTypes", "/Zc:templateScope", "/Zc:externConstexpr", "/Zc:lambda", "/Zc:throwingNew", "/volatile:iso", "/EHs-c-", "/GR-", "/fsanitize=address"] + +[options] +bigfoot/*:asan=True +bigfoot/*:build_tests=True diff --git a/README.md b/README.md index 5a66607..76bac37 100644 --- a/README.md +++ b/README.md @@ -48,14 +48,12 @@ You can customize these scripts to opt-out of some Bigfoot features 'Just' modify these lines to disable them (I promise to one day modify the script to do it from the command line) ``` --o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True +-o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True ``` 1. build_tests: Enable/Disable the tests 2. tracy: Enable/Disable profiling using [Tracy](https://github.com/wolfpld/tracy) -3. build_tools: Enable/Disable the tools (I'd absolutely recommand not disabling this) -4. vulkan: Enable/Disable Vulkan renderer (No point disabling it, since for now only Vulkan is available in Bigfoot) -5. build_benchmarks: Enable/Disable the benchmarks +3. vulkan: Enable/Disable Vulkan renderer (No point disabling it, since for now only Vulkan is available in Bigfoot) ### Generating Bigfoot diff --git a/Vendor/NatVis/EASTL/EASTL.natvis b/Vendor/NatVis/EASTL/EASTL.natvis index e1ec96d..472e834 100644 --- a/Vendor/NatVis/EASTL/EASTL.natvis +++ b/Vendor/NatVis/EASTL/EASTL.natvis @@ -103,20 +103,44 @@ - - [{mnSize}] {{}} - [{mnSize}] {{ {*mpData} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)}, {*(mpData+2)} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)}, {*(mpData+2)}, {*(mpData+3)} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)}, {*(mpData+2)}, {*(mpData+3)}, {*(mpData+4)} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)}, {*(mpData+2)}, {*(mpData+3)}, {*(mpData+4)}, {*(mpData+5)} }} - [{mnSize}] {{ {*mpData}, {*(mpData+1)}, {*(mpData+2)}, {*(mpData+3)}, {*(mpData+4)}, {*(mpData+5)}, ... }} + + [{mStorage.mnSize}] {{}} + [{mStorage.mnSize}] {{ {*mStorage.mpData} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)}, {*(mStorage.mpData+5)} }} + [{mStorage.mnSize}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)}, {*(mStorage.mpData+5)}, ... }} - mnSize + + DynamicSize + + mStorage.mnSize - mnSize - mpData + mStorage.mnSize + mStorage.mpData + + + + + + [{$T2}] {{}} + [{$T2}] {{ {*mStorage.mpData} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)}, {*(mStorage.mpData+5)} }} + [{$T2}] {{ {*mStorage.mpData}, {*(mStorage.mpData+1)}, {*(mStorage.mpData+2)}, {*(mStorage.mpData+3)}, {*(mStorage.mpData+4)}, {*(mStorage.mpData+5)}, ... }} + + + ConstantSize + + $T2 + + $T2 + mStorage.mpData @@ -319,6 +343,22 @@ + + [0] {{}} + [1] {{ {mAnchor.mpNext} }} + [?] {{ {mAnchor.mpNext}, ... }} + + + Content of intrusive lists will repeat indefinitely. Keep that in mind! + + + mAnchor.mpNext + mpNext + *(($T1*)this) + + + + {*($T1*)mpNode} @@ -537,6 +577,25 @@ + + {mError} (Error) + {mValue} + + mError + mValue + mHasValue + + + + + void + {mError} (Error) + + mError + mHasValue + + + {$T1} to {$T2}} @@ -606,70 +665,70 @@ [valueless_by_exception] - {{ index=0, value={($T1*)mStorage.mBuffer.mCharData}} - {{ index=1, value={($T2*)mStorage.mBuffer.mCharData}} - {{ index=2, value={($T3*)mStorage.mBuffer.mCharData}} - {{ index=3, value={($T4*)mStorage.mBuffer.mCharData}} - {{ index=4, value={($T5*)mStorage.mBuffer.mCharData}} - {{ index=5, value={($T6*)mStorage.mBuffer.mCharData}} - {{ index=6, value={($T7*)mStorage.mBuffer.mCharData}} - {{ index=7, value={($T8*)mStorage.mBuffer.mCharData}} - {{ index=8, value={($T9*)mStorage.mBuffer.mCharData}} - {{ index=9, value={($T10*)mStorage.mBuffer.mCharData}} - {{ index=10, value={($T11*)mStorage.mBuffer.mCharData}} - {{ index=11, value={($T12*)mStorage.mBuffer.mCharData}} - {{ index=12, value={($T13*)mStorage.mBuffer.mCharData}} - {{ index=13, value={($T14*)mStorage.mBuffer.mCharData}} - {{ index=14, value={($T15*)mStorage.mBuffer.mCharData}} - {{ index=15, value={($T16*)mStorage.mBuffer.mCharData}} - {{ index=16, value={($T17*)mStorage.mBuffer.mCharData}} - {{ index=17, value={($T18*)mStorage.mBuffer.mCharData}} - {{ index=18, value={($T19*)mStorage.mBuffer.mCharData}} - {{ index=19, value={($T20*)mStorage.mBuffer.mCharData}} - {{ index=20, value={($T21*)mStorage.mBuffer.mCharData}} - {{ index=21, value={($T22*)mStorage.mBuffer.mCharData}} - {{ index=22, value={($T23*)mStorage.mBuffer.mCharData}} - {{ index=23, value={($T24*)mStorage.mBuffer.mCharData}} - {{ index=24, value={($T25*)mStorage.mBuffer.mCharData}} - {{ index=25, value={($T26*)mStorage.mBuffer.mCharData}} - {{ index=26, value={($T27*)mStorage.mBuffer.mCharData}} - {{ index=27, value={($T28*)mStorage.mBuffer.mCharData}} - {{ index=28, value={($T29*)mStorage.mBuffer.mCharData}} - {{ index=29, value={($T30*)mStorage.mBuffer.mCharData}} - {{ index=30, value={($T31*)mStorage.mBuffer.mCharData}} + {{ index=0, value={($T1*)mBuffer.mCharData}} + {{ index=1, value={($T2*)mBuffer.mCharData}} + {{ index=2, value={($T3*)mBuffer.mCharData}} + {{ index=3, value={($T4*)mBuffer.mCharData}} + {{ index=4, value={($T5*)mBuffer.mCharData}} + {{ index=5, value={($T6*)mBuffer.mCharData}} + {{ index=6, value={($T7*)mBuffer.mCharData}} + {{ index=7, value={($T8*)mBuffer.mCharData}} + {{ index=8, value={($T9*)mBuffer.mCharData}} + {{ index=9, value={($T10*)mBuffer.mCharData}} + {{ index=10, value={($T11*)mBuffer.mCharData}} + {{ index=11, value={($T12*)mBuffer.mCharData}} + {{ index=12, value={($T13*)mBuffer.mCharData}} + {{ index=13, value={($T14*)mBuffer.mCharData}} + {{ index=14, value={($T15*)mBuffer.mCharData}} + {{ index=15, value={($T16*)mBuffer.mCharData}} + {{ index=16, value={($T17*)mBuffer.mCharData}} + {{ index=17, value={($T18*)mBuffer.mCharData}} + {{ index=18, value={($T19*)mBuffer.mCharData}} + {{ index=19, value={($T20*)mBuffer.mCharData}} + {{ index=20, value={($T21*)mBuffer.mCharData}} + {{ index=21, value={($T22*)mBuffer.mCharData}} + {{ index=22, value={($T23*)mBuffer.mCharData}} + {{ index=23, value={($T24*)mBuffer.mCharData}} + {{ index=24, value={($T25*)mBuffer.mCharData}} + {{ index=25, value={($T26*)mBuffer.mCharData}} + {{ index=26, value={($T27*)mBuffer.mCharData}} + {{ index=27, value={($T28*)mBuffer.mCharData}} + {{ index=28, value={($T29*)mBuffer.mCharData}} + {{ index=29, value={($T30*)mBuffer.mCharData}} + {{ index=30, value={($T31*)mBuffer.mCharData}} index() - ($T1*)mStorage.mBuffer.mCharData - ($T2*)mStorage.mBuffer.mCharData - ($T3*)mStorage.mBuffer.mCharData - ($T4*)mStorage.mBuffer.mCharData - ($T5*)mStorage.mBuffer.mCharData - ($T6*)mStorage.mBuffer.mCharData - ($T7*)mStorage.mBuffer.mCharData - ($T8*)mStorage.mBuffer.mCharData - ($T9*)mStorage.mBuffer.mCharData - ($T10*)mStorage.mBuffer.mCharData - ($T11*)mStorage.mBuffer.mCharData - ($T12*)mStorage.mBuffer.mCharData - ($T13*)mStorage.mBuffer.mCharData - ($T14*)mStorage.mBuffer.mCharData - ($T15*)mStorage.mBuffer.mCharData - ($T16*)mStorage.mBuffer.mCharData - ($T17*)mStorage.mBuffer.mCharData - ($T18*)mStorage.mBuffer.mCharData - ($T19*)mStorage.mBuffer.mCharData - ($T20*)mStorage.mBuffer.mCharData - ($T21*)mStorage.mBuffer.mCharData - ($T22*)mStorage.mBuffer.mCharData - ($T23*)mStorage.mBuffer.mCharData - ($T24*)mStorage.mBuffer.mCharData - ($T25*)mStorage.mBuffer.mCharData - ($T26*)mStorage.mBuffer.mCharData - ($T27*)mStorage.mBuffer.mCharData - ($T28*)mStorage.mBuffer.mCharData - ($T29*)mStorage.mBuffer.mCharData - ($T30*)mStorage.mBuffer.mCharData - ($T31*)mStorage.mBuffer.mCharData + ($T1*)mBuffer.mCharData + ($T2*)mBuffer.mCharData + ($T3*)mBuffer.mCharData + ($T4*)mBuffer.mCharData + ($T5*)mBuffer.mCharData + ($T6*)mBuffer.mCharData + ($T7*)mBuffer.mCharData + ($T8*)mBuffer.mCharData + ($T9*)mBuffer.mCharData + ($T10*)mBuffer.mCharData + ($T11*)mBuffer.mCharData + ($T12*)mBuffer.mCharData + ($T13*)mBuffer.mCharData + ($T14*)mBuffer.mCharData + ($T15*)mBuffer.mCharData + ($T16*)mBuffer.mCharData + ($T17*)mBuffer.mCharData + ($T18*)mBuffer.mCharData + ($T19*)mBuffer.mCharData + ($T20*)mBuffer.mCharData + ($T21*)mBuffer.mCharData + ($T22*)mBuffer.mCharData + ($T23*)mBuffer.mCharData + ($T24*)mBuffer.mCharData + ($T25*)mBuffer.mCharData + ($T26*)mBuffer.mCharData + ($T27*)mBuffer.mCharData + ($T28*)mBuffer.mCharData + ($T29*)mBuffer.mCharData + ($T30*)mBuffer.mCharData + ($T31*)mBuffer.mCharData @@ -757,5 +816,94 @@ + + (nothing) + {static_cast<flag_type>(m_mask),en} + + m_mask,bb + (flag_type)(1 << 0),en + (flag_type)(1 << 1),en + (flag_type)(1 << 2),en + (flag_type)(1 << 3),en + (flag_type)(1 << 4),en + (flag_type)(1 << 5),en + (flag_type)(1 << 6),en + (flag_type)(1 << 7),en - + (flag_type)(1 << 8),en + (flag_type)(1 << 9),en + (flag_type)(1 << 10),en + (flag_type)(1 << 11),en + (flag_type)(1 << 12),en + (flag_type)(1 << 13),en + (flag_type)(1 << 14),en + (flag_type)(1 << 15),en + + (flag_type)(1 << 16),en + (flag_type)(1 << 17),en + (flag_type)(1 << 18),en + (flag_type)(1 << 19),en + (flag_type)(1 << 20),en + (flag_type)(1 << 21),en + (flag_type)(1 << 22),en + (flag_type)(1 << 23),en + + (flag_type)(1 << 24),en + (flag_type)(1 << 25),en + (flag_type)(1 << 26),en + (flag_type)(1 << 27),en + (flag_type)(1 << 28),en + (flag_type)(1 << 29),en + (flag_type)(1 << 30),en + (flag_type)(1 << 31),en + + + + + + {static_cast<flag_type>(__log2(m_mask)),en} + + (multiple values) + + (nothing) + + + m_mask,bb + (flag_type)(0),en + (flag_type)(1),en + (flag_type)(2),en + (flag_type)(3),en + (flag_type)(4),en + (flag_type)(5),en + (flag_type)(6),en + (flag_type)(7),en + + (flag_type)(8),en + (flag_type)(9),en + (flag_type)(10),en + (flag_type)(11),en + (flag_type)(12),en + (flag_type)(13),en + (flag_type)(14),en + (flag_type)(15),en + + (flag_type)(16),en + (flag_type)(17),en + (flag_type)(18),en + (flag_type)(19),en + (flag_type)(20),en + (flag_type)(21),en + (flag_type)(22),en + (flag_type)(23),en + + (flag_type)(24),en + (flag_type)(25),en + (flag_type)(26),en + (flag_type)(27),en + (flag_type)(28),en + (flag_type)(29),en + (flag_type)(30),en + (flag_type)(31),en + + + \ No newline at end of file diff --git a/Vendor/NatVis/GLM/GLM.natvis b/Vendor/NatVis/GLM/GLM.natvis index 018be8d..349d3c1 100644 --- a/Vendor/NatVis/GLM/GLM.natvis +++ b/Vendor/NatVis/GLM/GLM.natvis @@ -403,7 +403,7 @@ - + [{value[0]} {value[1]} {value[2]}] diff --git a/Vendor/Tools/Tracy0.13.1/tracy-capture.exe b/Vendor/Tools/Tracy0.13.1/tracy-capture.exe new file mode 100644 index 0000000..9dd844d Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-capture.exe differ diff --git a/Vendor/Tools/Tracy0.13.1/tracy-csvexport.exe b/Vendor/Tools/Tracy0.13.1/tracy-csvexport.exe new file mode 100644 index 0000000..0811b9c Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-csvexport.exe differ diff --git a/Vendor/Tools/Tracy0.13.1/tracy-import-chrome.exe b/Vendor/Tools/Tracy0.13.1/tracy-import-chrome.exe new file mode 100644 index 0000000..7de4d00 Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-import-chrome.exe differ diff --git a/Vendor/Tools/Tracy0.13.1/tracy-import-fuchsia.exe b/Vendor/Tools/Tracy0.13.1/tracy-import-fuchsia.exe new file mode 100644 index 0000000..9f64f42 Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-import-fuchsia.exe differ diff --git a/Vendor/Tools/Tracy0.13.1/tracy-profiler.exe b/Vendor/Tools/Tracy0.13.1/tracy-profiler.exe new file mode 100644 index 0000000..6d0d858 Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-profiler.exe differ diff --git a/Vendor/Tools/Tracy0.13.1/tracy-update.exe b/Vendor/Tools/Tracy0.13.1/tracy-update.exe new file mode 100644 index 0000000..8db7dec Binary files /dev/null and b/Vendor/Tools/Tracy0.13.1/tracy-update.exe differ diff --git a/conanfile.py b/conanfile.py index ffad6ab..11a4c44 100644 --- a/conanfile.py +++ b/conanfile.py @@ -18,20 +18,24 @@ class Bigfoot(ConanFile): options = { "shared": [True, False], "fPIC": [True, False], + "asan": [True, False], + "coverage": [True, False], "build_tests": [True, False], - "tracy": [True, False], - "build_tools": [True, False], - "vulkan": [True, False], "build_benchmarks": [True, False], + "tracy": [True, False], + "vulkan": [True, False], + "toolmode": [True, False] } default_options = { "shared": False, "fPIC": True, + "asan": False, + "coverage": False, "build_tests": False, - "tracy": False, - "build_tools": True, - "vulkan": True, "build_benchmarks": False, + "tracy": False, + "vulkan": True, + "toolmode": True, } generators = "CMakeDeps" @@ -43,10 +47,8 @@ class Bigfoot(ConanFile): if self.settings.os == "Windows": del self.options.fPIC - self.options['mimalloc'].override = True - self.options['mimalloc'].shared = True - if(self.settings.os == "Windows"): - self.options["mimalloc"].win_redirect = True + if(self.options.asan): + self.options["mimalloc"].asan = True self.options['stduuid'].with_cxx20_span = True self.options['flatbuffers'].header_only = True @@ -56,65 +58,54 @@ class Bigfoot(ConanFile): if(self.options.vulkan): self.options["spirv-cross"].exceptions = False - - if(self.options.build_benchmarks): - self.options["benchmark"].enable_exceptions = False - self.options["benchmark"].enable_lto = True + + def build_requirements(self): + self.tool_requires("bin2cpp/1.0.0@bigfootdev/main") def requirements(self): self.requires("eastl/3.27.01@bigfootdev/main", transitive_headers=True) - self.requires("unordered_dense/4.8.1@bigfootdev/main", transitive_headers=True) - self.requires("mimalloc/3.1.5@bigfootdev/main", transitive_headers=True) + self.requires("eastdc/25082026@bigfootdev/main", transitive_headers=True) + self.requires("unordered_dense/4.9.2", transitive_headers=True) + self.requires("mimalloc/3.3.2@bigfootdev/main", transitive_headers=True) self.requires("stduuid/1.2.3@bigfootdev/main", transitive_headers=True) - self.requires("sqlite3/3.51.0@bigfootdev/main", transitive_headers=True) - self.requires("cli11/2.6.0") + self.requires("sqlite3/3.53.3@bigfootdev/main", transitive_headers=True) self.requires("rapidhash/3.0@bigfootdev/main", transitive_headers=True) self.requires("effolkronium-random/1.5.0", transitive_headers=True) - self.requires("flatbuffers/25.12.19@bigfootdev/main", transitive_headers=True) + self.requires("fmt/12.2.0", transitive_headers=True) + self.requires("flatbuffers/25.12.19-2026-02-06-03fffb2-bigfoot@bigfootdev/main", transitive_headers=True) + self.requires("glm/1.0.3", transitive_headers=True) + self.requires("lodepng/cci.20260210@bigfootdev/main", transitive_headers=True) + self.requires("imgui/1.92.9b-docking", transitive_headers=True) + self.requires("lyra/1.8.0", transitive_headers=True) if(self.settings.build_type == "RelWithDebInfo" or self.settings.build_type == "Debug"): - self.requires("quill/11.0.2", transitive_headers=True) - self.requires("cpptrace/1.0.4", transitive_headers=True) + self.requires("quill/12.1.0", transitive_headers=True) if(self.options.tracy): - self.requires("tracy/0.12.2", transitive_headers=True) - - self.requires("glm/1.0.1", transitive_headers=True) - self.requires("lodepng/cci.20250727@bigfootdev/main", transitive_headers=True) - self.requires("imgui/1.92.5-docking", transitive_headers=True) + self.requires("tracy/0.13.1", transitive_headers=True) if(self.options.vulkan): - self.requires("vulkan-headers/1.4.313.0") - if(self.settings.build_type == "RelWithDebInfo" or self.settings.build_type == "Debug"): - self.requires("vulkan-validationlayers/1.4.313.0@bigfootdev/main") + self.requires("vulkan-headers/1.4.341.0@bigfootdev/main", override=True) self.requires("vulkan-memory-allocator/3.3.0@bigfootdev/main") - if(self.options.build_tests or self.options.build_benchmarks): - self.requires("glfw/3.4") - if(self.options.build_tests): - self.test_requires("gtest/1.17.0") + self.test_requires("gtest/1.18.0") self.test_requires("pixelmatch-cpp17/1.0.3@bigfootdev/main") - if(self.options.build_tools): - self.requires("spirv-cross/1.4.313.0") - self.requires("shaderc/2025.3@bigfootdev/main") - self.requires("stb/cci.20240531", override=True) - self.requires("assimp/6.0.2") - self.requires("meshoptimizer/1.0@bigfootdev/main") - self.requires("libsquish/1.15") - if(self.options.build_benchmarks): - self.requires("benchmark/1.9.4") + self.requires("benchmark/1.9.5") def generate(self): tc = CMakeToolchain(self) - + tc.presets_prefix = "bigfoot-tool" if self.options.toolmode else "bigfoot-game" + + tc.variables["ASAN"] = self.options.asan + tc.variables["COVERAGE"] = self.options.coverage tc.variables["BUILD_TESTS"] = self.options.build_tests - tc.variables["TRACY"] = self.options.tracy - tc.variables["BUILD_TOOLS"] = self.options.build_tools - tc.variables["VULKAN"] = self.options.vulkan tc.variables["BUILD_BENCHMARKS"] = self.options.build_benchmarks + tc.variables["TRACY"] = self.options.tracy + tc.variables["VULKAN"] = self.options.vulkan + tc.variables["TOOLMODE"] = self.options.toolmode tc.generate() diff --git a/generate_bigfoot.bat b/generate_bigfoot.bat index 0f4298f..cf89f2d 100644 --- a/generate_bigfoot.bat +++ b/generate_bigfoot.bat @@ -1 +1,66 @@ -cmake -S . -B build --toolchain build/generators/conan_toolchain.cmake --graphviz=graphviz/graph.dot \ No newline at end of file +@echo off +setlocal enabledelayedexpansion + +REM ─── Validate argument ─────────────────────────────────────────────────────── +if "%~1"=="force" set "build_option=--build=*" & goto :start +if "%~1"=="missing" set "build_option=--build=missing" & goto :start + +echo Usage: %~n0 [force^|missing] +echo force - Rebuild all packages from source +echo missing - Only build packages not already cached +exit /b 1 + +:start +REM ─── Register remote (skip if already registered) ──────────────────────────── +conan remote list | findstr /i "bigfootpackages" >nul 2>&1 +if errorlevel 1 ( + echo Adding Conan remote: bigfootpackages + conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages + if errorlevel 1 ( + echo ERROR: Failed to add Conan remote. + exit /b 1 + ) +) else ( + echo Conan remote 'bigfootpackages' already registered, skipping. +) + +REM ─── Shared flags ──────────────────────────────────────────────────────────── +set "conan_common_base=--remote=bigfootpackages -pr:h=./ConanProfiles/host/msvc -pr:b=./ConanProfiles/build/msvc -o bigfoot/*:build_tests=True -o bigfoot/*:build_benchmarks=True -o bigfoot/*:tracy=True -o bigfoot/*:vulkan=True" + +REM ─── Install dependencies for each build type/variant ──────────────────────── +for %%C in (Release RelWithDebInfo Debug) do ( + echo. + echo [%%C] Installing dependencies... + for %%V in (toolmode game) do ( + set "toolmode_val=False" + if "%%V"=="toolmode" set "toolmode_val=True" + conan install . !conan_common_base! -of build-windows-%%V -o bigfoot/*:toolmode=!toolmode_val! %build_option% -s:h build_type=%%C + if errorlevel 1 ( + echo ERROR: conan install failed for build type %%C ^(%%V^) + exit /b 1 + ) + ) + echo [%%C] Done. +) + +echo. +echo All build types installed successfully. + +REM ─── Activate build environment and run CMake ──────────────────────────────── +echo. +echo Activating build environment and configuring CMake... + +for %%V in (toolmode game) do ( + mkdir graphviz-windows-%%V 2>nul + + powershell -ExecutionPolicy Bypass -Command "& 'build-windows-%%V/build/generators/conanbuild.ps1'; cmake -S . -B build-windows-%%V --toolchain build-windows-%%V/build/generators/conan_toolchain.cmake --graphviz=graphviz-windows-%%V/graph.dot; if ($LASTEXITCODE -ne 0) { Write-Host 'ERROR: CMake configuration failed'; exit 1 }" + if errorlevel 1 ( + echo ERROR: Build environment or CMake step failed for %%V. + exit /b 1 + ) +) + +echo. +echo CMake configuration successful. + +endlocal diff --git a/generate_bigfoot.sh b/generate_bigfoot.sh index 1b72527..0a5bff7 100644 --- a/generate_bigfoot.sh +++ b/generate_bigfoot.sh @@ -1,3 +1,124 @@ -cmake -S . -B build/Debug --toolchain build/build/Debug/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="Debug" -G "Ninja" --graphviz=graphviz/Debug/graph.dot -cmake -S . -B build/Release --toolchain build/build/Release/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="Release" -G "Ninja" --graphviz=graphviz/Release/graph.dot -cmake -S . -B build/RelWithDebInfo --toolchain build/build/RelWithDebInfo/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE="RelWithDebInfo" -G "Ninja" --graphviz=graphviz/RelWithDebInfo/graph.dot \ No newline at end of file +#!/usr/bin/env bash +set -euo pipefail + +# ─── Validate arguments ─────────────────────────────────────────────────────── +if [[ $# -lt 1 ]]; then + echo "Usage: $(basename "$0") [force|missing] [Release|RelWithDebInfo|Debug]" + echo " force - Rebuild all packages from source" + echo " missing - Only build packages not already cached" + echo " build_type (optional) - One of: Release, RelWithDebInfo, Debug" + echo " If omitted, all build types are processed" + exit 1 +fi + +case "${1:-}" in + force) build_option="--build=*" ;; + missing) build_option="--build=missing" ;; + *) + echo "Usage: $(basename "$0") [force|missing] [Release|RelWithDebInfo|Debug]" + echo " force - Rebuild all packages from source" + echo " missing - Only build packages not already cached" + exit 1 + ;; +esac + +# ─── Validate optional build_type argument ──────────────────────────────────── +all_build_types=(Release RelWithDebInfo Debug) + +if [[ -n "${2:-}" ]]; then + case "${2}" in + Release|RelWithDebInfo|Debug) + build_types=("${2}") + ;; + *) + echo "ERROR: Invalid build type '${2}'. Must be one of: Release, RelWithDebInfo, Debug" + exit 1 + ;; + esac +else + build_types=("${all_build_types[@]}") +fi + +# ─── Register remote (skip if already registered) ───────────────────────────── +if conan remote list 2>/dev/null | grep -qi "bigfootpackages"; then + echo "Conan remote 'bigfootpackages' already registered, skipping." +else + echo "Adding Conan remote: bigfootpackages" + if ! conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages; then + echo "ERROR: Failed to add Conan remote." + exit 1 + fi +fi + +# ─── Shared flags ───────────────────────────────────────────────────────────── +conan_common_toolmode=( + --remote=bigfootpackages + -pr:h=./ConanProfiles/host/clang + -pr:b=./ConanProfiles/build/clang + -of build-linux-toolmode + -o "bigfoot/*:build_tests=True" + -o "bigfoot/*:build_benchmarks=True" + -o "bigfoot/*:tracy=True" + -o "bigfoot/*:vulkan=True" + -o "bigfoot/*:toolmode=True" +) + +conan_common_game=( + --remote=bigfootpackages + -pr:h=./ConanProfiles/host/clang + -pr:b=./ConanProfiles/build/clang + -of build-linux-game + -o "bigfoot/*:build_tests=True" + -o "bigfoot/*:build_benchmarks=True" + -o "bigfoot/*:tracy=True" + -o "bigfoot/*:vulkan=True" + -o "bigfoot/*:toolmode=False" +) + +# ─── Install dependencies for each build type ───────────────────────────────── +for build_type in "${build_types[@]}"; do + echo + echo "[${build_type}] Installing dependencies..." + if ! conan install . "${conan_common_toolmode[@]}" ${build_option} -s:h "build_type=${build_type}"; then + echo "ERROR: conan install failed for build type ${build_type}" + exit 1 + fi + if ! conan install . "${conan_common_game[@]}" ${build_option} -s:h "build_type=${build_type}"; then + echo "ERROR: conan install failed for build type ${build_type}" + exit 1 + fi + echo "[${build_type}] Done." +done + +echo +echo "All build types installed successfully." + +# ─── Configure CMake for each build type ───────────────────────────────────── +echo +echo "Configuring CMake for each build type..." + +for build_type in "${build_types[@]}"; do + echo + echo "[${build_type}] Activating build environment..." + + for variant in toolmode game; do + # shellcheck disable=SC1090 + source "build-linux-${variant}/build/${build_type}/generators/conanbuild.sh" + + mkdir -p "graphviz-linux-${variant}/${build_type}" + + echo "[${build_type}] Running CMake (${variant})..." + if ! cmake -S . -B "build-linux-${variant}/${build_type}" \ + --toolchain "build-linux-${variant}/build/${build_type}/generators/conan_toolchain.cmake" \ + -DCMAKE_BUILD_TYPE="${build_type}" \ + -G "Ninja" \ + --graphviz="graphviz-linux-${variant}/${build_type}/graph.dot"; then + echo "ERROR: CMake configuration failed for build type ${build_type} (${variant})." + exit 1 + fi + done + echo "[${build_type}] Done." +done + +echo +echo "CMake configuration successful." \ No newline at end of file diff --git a/generate_dependencies.bat b/generate_dependencies.bat deleted file mode 100644 index f7de8c6..0000000 --- a/generate_dependencies.bat +++ /dev/null @@ -1,33 +0,0 @@ -@echo off -setlocal - -REM Check if the correct number of arguments is provided -if "%~1"=="" ( - echo Usage: %0 "[force|missing]" - exit /b 1 -) - -REM Set the build option based on the argument -set build_option= -if "%~1"=="force" ( - set build_option=--build="*" -) else if "%~1"=="missing" ( - set build_option=--build=missing -) else ( - echo Invalid argument: %~1 - echo Usage: %0 "[force|missing]" - exit /b 1 -) - -REM Add the remote -conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages - -REM Install the conan configuration -conan config install https://git.romainboullard.com/BigfootDev/ConanProfiles.git - -REM Install dependencies with the specified build option -conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=msvc -pr:b=msvc %build_option% -of build -s build_type=Release -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True -conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=msvcd -pr:b=msvcd %build_option% -of build -s build_type=RelWithDebInfo -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True -conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=msvcd -pr:b=msvcd %build_option% -of build -s build_type=Debug -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - -endlocal diff --git a/generate_dependencies.sh b/generate_dependencies.sh deleted file mode 100644 index 6d90476..0000000 --- a/generate_dependencies.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Check if the correct number of arguments is provided -if [ -z "$1" ]; then - echo "Usage: $0 [force|missing]" - exit 1 -fi - -# Add the remote -conan remote add bigfootpackages https://conan.romainboullard.com/artifactory/api/conan/BigfootPackages - -# Install the conan configuration -conan config install https://git.romainboullard.com/BigfootDev/ConanProfiles.git - -# Set the build option based on the argument -if [ "$1" == "force" ]; then - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clang -pr:b=clang --build='*' -of build -s build_type=Release -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clangd -pr:b=clangd --build='*' -of build -s build_type=RelWithDebInfo -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clangd -pr:b=clangd --build='*' -of build -s build_type=Debug -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True -elif [ "$1" == "missing" ]; then - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clang -pr:b=clang --build=missing -of build -s build_type=Release -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clangd -pr:b=clangd --build=missing -of build -s build_type=RelWithDebInfo -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True - conan install . --deployer=full_deploy --remote=bigfootpackages -pr:h=clangd -pr:b=clangd --build=missing -of build -s build_type=Debug -o bigfoot/*:build_tests=True -o bigfoot/*:tracy=True -o bigfoot/*:build_tools=True -o bigfoot/*:vulkan=True -o bigfoot/*:build_benchmarks=True -else - echo "Invalid argument: $1" - echo "Usage: $0 [force|missing]" - exit 1 -fi diff --git a/sonar-project.properties b/sonar-project.properties index 6ff664b..9fe172c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,5 +10,8 @@ sonar.tests=Bigfoot/Tests sonar.cxx.file.suffixes=.hpp,.cpp,.h sonar.cxx.clangtidy.reportPaths=tidy_result.txt sonar.cxx.infer.reportPaths=infer-out/report.json +sonar.cxx.xunit.reportPaths=build-toolmode/Debug/TestResults/*.xml +sonar.cxx.cobertura.reportPaths=coverage.xml +sonar.coverage.exclusions=**/*_generated.hpp sonar.cxx.jsonCompilationDatabase.analyzeOnlyContainedFiles=True