BigFile/Sasquatch (#1)
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 44s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 29s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 46s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 30s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m21s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 1m3s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m25s
Bigfoot / Build & Test Debug with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 1m8s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 1m9s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m34s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 1m13s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 37s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 28s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 57s
Bigfoot / Sonarqube (push) Successful in 6m0s
sonarqube ERROR
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: game) (push) Successful in 49s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: game) (push) Successful in 30s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 52s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 36s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m23s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang (Unity Build: ON, Variant: toolmode) (push) Successful in 44s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 59s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: game) (push) Successful in 51s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m5s
Bigfoot / Build & Test Release with ./ConanProfiles/host/clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 56s
Bigfoot / Clang Format Checks (push) Successful in 13s
Bigfoot / Sonarqube (pull_request) Failing after 5m14s

Implementing an asset management system

BigFile is a sqlite database, for toolmode
Sasquatch is a custom binary file, for gamemode

Assets are built from flatbuffers
References allow to get an Asset, from an UUID, from anywhere in the code
HardReference refcount the asset and allow automatic removal

Reviewed-on: #1
Co-authored-by: Romain BOULLARD <romain.boullard@protonmail.com>
This commit was merged in pull request #1.
This commit is contained in:
2026-09-01 16:32:01 +00:00
committed by rboullard
parent e825728867
commit b7f4677c68
207 changed files with 17173 additions and 1502 deletions
+43 -2
View File
@@ -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
KeepEmptyLinesAtTheStartOfBlocks: false
IncludeCategories:
- Regex: '^<Engine/'
Priority: 1
- Regex: '^<System/'
Priority: 2
- Regex: '^<Utils/'
Priority: 3
- Regex: '^<SasquatchBaker/'
Priority: 4
- Regex: '^<EngineTests/'
Priority: 5
- Regex: '^<SystemTests/'
Priority: 6
- Regex: '^<UtilsTests/'
Priority: 7
- Regex: '^<AssetsForTesting/'
Priority: 8
- Regex: '^<SasquatchBakerTests/'
Priority: 9
- Regex: '^<EngineBenchmarks/'
Priority: 10
- Regex: '^<AssetsForBenchmarking/'
Priority: 11
- Regex: '^<SasquatchBakerBenchmarks/'
Priority: 12
- Regex: '^<[a-zA-Z0-9_/]+\.h>'
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'
+4 -1
View File
@@ -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.*'
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "Bigfoot",
"image": "git.romainboullard.com/bigfootdev/linuxbigfootbuilder:main"
"image": "git.romainboullard.com/bigfootdev/linuxcppbuilder:development"
}
+16 -16
View File
@@ -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
+46 -11
View File
@@ -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 }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST }}
+3 -1
View File
@@ -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
graphviz*
@@ -0,0 +1,11 @@
/*********************************************************************
* \file BenchmarkAsset.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <Engine/Asset/AssetHelper.hpp>
BIGFOOT_ASSET(BenchmarkAsset, ::Bigfoot::BenchmarkAsset)
@@ -0,0 +1,11 @@
/*********************************************************************
* \file BenchmarkAssetReferred.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp>
#include <Engine/Asset/AssetHelper.hpp>
BIGFOOT_ASSET(BenchmarkAssetReferred, ::Bigfoot::BenchmarkAssetReferred)
@@ -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)
@@ -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;
@@ -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 <Engine/Asset/Asset.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset_generated.hpp>
namespace Bigfoot
{
struct BenchmarkAssetTraits
{
using FLAT = ::Flat::Bigfoot::BenchmarkAsset;
};
class BenchmarkAsset: public Asset<BenchmarkAssetTraits>
{
};
} // namespace Bigfoot
#endif
@@ -0,0 +1,7 @@
namespace Flat.Bigfoot;
table BenchmarkAssetReferred
{
}
root_type BenchmarkAssetReferred;
@@ -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 <Engine/Asset/Asset.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAssetReferred_generated.hpp>
namespace Bigfoot
{
struct BenchmarkAssetReferredTraits
{
using FLAT = ::Flat::Bigfoot::BenchmarkAssetReferred;
};
class BenchmarkAssetReferred: public Asset<BenchmarkAssetReferredTraits>
{
};
} // namespace Bigfoot
#endif
@@ -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 B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &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<BenchmarkAssetReferred> 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<BenchmarkAssetReferred> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<BenchmarkAssetReferred>(end);
return o;
}
};
inline ::flatbuffers::Offset<BenchmarkAssetReferred> CreateBenchmarkAssetReferred(
::flatbuffers::FlatBufferBuilder &_fbb) {
BenchmarkAssetReferredBuilder builder_(_fbb);
return builder_.Finish();
}
struct BenchmarkAssetReferred::Traits {
using type = BenchmarkAssetReferred;
static auto constexpr Create = CreateBenchmarkAssetReferred;
};
::flatbuffers::Offset<BenchmarkAssetReferred> 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<BenchmarkAssetReferredT>();
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<BenchmarkAssetReferred> CreateBenchmarkAssetReferred(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetReferredT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) {
return BenchmarkAssetReferred::Pack(_fbb, _o, _rehasher);
}
inline ::flatbuffers::Offset<BenchmarkAssetReferred> 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<Flat::Bigfoot::BenchmarkAssetReferred>(buf);
}
inline const Flat::Bigfoot::BenchmarkAssetReferred *GetSizePrefixedBenchmarkAssetReferred(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Flat::Bigfoot::BenchmarkAssetReferred>(buf);
}
template <bool B = false>
inline bool VerifyBenchmarkAssetReferredBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifyBuffer<Flat::Bigfoot::BenchmarkAssetReferred>(nullptr);
}
template <bool B = false>
inline bool VerifySizePrefixedBenchmarkAssetReferredBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifySizePrefixedBuffer<Flat::Bigfoot::BenchmarkAssetReferred>(nullptr);
}
inline const char *BenchmarkAssetReferredExtension() {
return "bfbs";
}
inline void FinishBenchmarkAssetReferredBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::BenchmarkAssetReferred> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedBenchmarkAssetReferredBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::BenchmarkAssetReferred> root) {
fbb.FinishSizePrefixed(root);
}
inline eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetReferredT> UnPackBenchmarkAssetReferred(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetReferredT>(GetBenchmarkAssetReferred(buf)->UnPack(res));
}
inline eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetReferredT> UnPackSizePrefixedBenchmarkAssetReferred(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetReferredT>(GetSizePrefixedBenchmarkAssetReferred(buf)->UnPack(res));
}
} // namespace Bigfoot
} // namespace Flat
#endif // FLATBUFFERS_GENERATED_BENCHMARKASSETREFERRED_FLAT_BIGFOOT_H_
@@ -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<uint8_t> 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<uint8_t> *payload() const {
return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_PAYLOAD);
}
const ::flatbuffers::Vector<const Flat::Bigfoot::HardReference *> *refs() const {
return GetPointer<const ::flatbuffers::Vector<const Flat::Bigfoot::HardReference *> *>(VT_REFS);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &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<BenchmarkAsset> 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<uint8_t>> payload) {
fbb_.AddOffset(BenchmarkAsset::VT_PAYLOAD, payload);
}
void add_refs(::flatbuffers::Offset<::flatbuffers::Vector<const Flat::Bigfoot::HardReference *>> refs) {
fbb_.AddOffset(BenchmarkAsset::VT_REFS, refs);
}
explicit BenchmarkAssetBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<BenchmarkAsset> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<BenchmarkAsset>(end);
return o;
}
};
inline ::flatbuffers::Offset<BenchmarkAsset> CreateBenchmarkAsset(
::flatbuffers::FlatBufferBuilder &_fbb,
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> payload = 0,
::flatbuffers::Offset<::flatbuffers::Vector<const Flat::Bigfoot::HardReference *>> 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<BenchmarkAsset> 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<BenchmarkAssetT>();
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<BenchmarkAsset> CreateBenchmarkAsset(::flatbuffers::FlatBufferBuilder &_fbb, const BenchmarkAssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) {
return BenchmarkAsset::Pack(_fbb, _o, _rehasher);
}
inline ::flatbuffers::Offset<BenchmarkAsset> 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<Flat::Bigfoot::HardReference, ::Bigfoot::HardReference<::Bigfoot::BenchmarkAssetReferred>>(_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<Flat::Bigfoot::BenchmarkAsset>(buf);
}
inline const Flat::Bigfoot::BenchmarkAsset *GetSizePrefixedBenchmarkAsset(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Flat::Bigfoot::BenchmarkAsset>(buf);
}
template <bool B = false>
inline bool VerifyBenchmarkAssetBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifyBuffer<Flat::Bigfoot::BenchmarkAsset>(nullptr);
}
template <bool B = false>
inline bool VerifySizePrefixedBenchmarkAssetBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifySizePrefixedBuffer<Flat::Bigfoot::BenchmarkAsset>(nullptr);
}
inline const char *BenchmarkAssetExtension() {
return "bfbs";
}
inline void FinishBenchmarkAssetBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::BenchmarkAsset> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedBenchmarkAssetBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::BenchmarkAsset> root) {
fbb.FinishSizePrefixed(root);
}
inline eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetT> UnPackBenchmarkAsset(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetT>(GetBenchmarkAsset(buf)->UnPack(res));
}
inline eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetT> UnPackSizePrefixedBenchmarkAsset(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::BenchmarkAssetT>(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_
+7
View File
@@ -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()
@@ -0,0 +1,175 @@
/*********************************************************************
* \file AssetContainer.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Asset/AssetContainer.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstdint>
namespace Bigfoot
{
namespace
{
using Random = effolkronium::random_static;
} // namespace
class AssetContainerFixture: public benchmark::Fixture
{
protected:
[[nodiscard]]
static eastl::vector<UUID> InsertRandomAssets(AssetContainer<BenchmarkAsset>& p_container,
const std::uint32_t p_count)
{
eastl::vector<UUID> 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<BenchmarkAsset> m_container;
};
/****************************************************************************************/
BENCHMARK_DEFINE_F(AssetContainerFixture, Insert)(benchmark::State& state)
{
const auto count = static_cast<std::uint32_t>(state.range(0));
for (auto _: state)
{
AssetContainer<BenchmarkAsset> 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<std::int64_t>(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<std::uint32_t>(state.range(0));
const eastl::vector<UUID> uuids = InsertRandomAssets(m_container, count);
eastl::vector<ContainerHandle*> 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<const BenchmarkAsset*>(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<std::uint32_t>(state.range(0));
const eastl::vector<UUID> 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<std::uint32_t>(state.range(0));
const eastl::vector<UUID> 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<std::uint32_t>(state.range(0));
eastl::vector<UUID> 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<std::int64_t>(state.iterations() * count * sizeof(BenchmarkAsset)));
}
BENCHMARK_REGISTER_F(AssetContainerFixture, Remove)->Name("AssetContainer/Remove")->Arg(512)->Arg(8192)->Arg(131'072);
} // namespace Bigfoot
@@ -0,0 +1,187 @@
/*********************************************************************
* \file Reference.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Asset/Reference.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <EASTL/optional.h>
#include <EASTL/vector.h>
#include <benchmark/benchmark.h>
#include <cstddef>
#include <utility>
namespace Bigfoot
{
template<template<class> class REFERENCE>
class ReferenceFixture: public benchmark::Fixture
{
protected:
using ReferenceType = REFERENCE<BenchmarkAsset>;
void SetUp(benchmark::State&) override
{
SetActiveAssetLibrary(&m_library);
std::ignore = m_library.Insert(&m_container);
m_uuid = CreateAsset<BenchmarkAsset>()->GetHeader().uuid;
m_reference.emplace(m_uuid);
}
void TearDown(benchmark::State&) override
{
SetActiveAssetLibrary(&g_assetLibrary);
}
AssetLibrary m_library;
AssetContainer<BenchmarkAsset> m_container;
UUID m_uuid;
eastl::optional<ReferenceType> 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<ReferenceType> 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<ReferenceType> 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
@@ -0,0 +1,312 @@
/*********************************************************************
* \file BigFile.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/BigFile/BigFile.hpp>
#include <System/File/File.hpp>
#include <System/UUID/UUID.hpp>
#include <EngineBenchmarks/BigFileInfo_generated.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp>
#include <EASTL/optional.h>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstddef>
#include <cstdint>
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<UUID> RandomBatch(const std::size_t p_batchSize) const
{
eastl::vector<UUID> batch;
batch.reserve(p_batchSize);
for (std::size_t i = 0; i < p_batchSize; ++i)
{
batch.push_back(m_uuids[Random::get<std::size_t>(0, m_uuids.size() - 1)]);
}
return batch;
}
[[nodiscard]]
eastl::vector<eastl::vector<UUID>> RandomBatchPool(const std::size_t p_batchSize,
const std::size_t p_poolSize) const
{
eastl::vector<eastl::vector<UUID>> 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<BigFile> m_bigFile;
eastl::span<const UUID> m_uuids;
private:
static const eastl::vector<UUID>& BuildData()
{
static const eastl::vector<UUID> s_uuids = []()
{
eastl::vector<UUID> uuids;
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::byte> 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<std::byte> flatbuffer = m_bigFile->GetAssetFlatbuffer(
m_uuids[Random::get<std::size_t>(0, m_uuids.size() - 1)]);
benchmark::DoNotOptimize(flatbuffer);
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(state.range(0));
constexpr std::size_t BATCH_POOL_SIZE = 64;
const eastl::vector<eastl::vector<UUID>> batchPool = RandomBatchPool(batchSize, BATCH_POOL_SIZE);
std::int64_t bytesProcessed = 0;
std::size_t batchIndex = 0;
for (auto _: state)
{
eastl::vector<eastl::vector<std::byte>> flatbuffers = m_bigFile->GetAssetFlatbuffers(batchPool[batchIndex]);
benchmark::DoNotOptimize(flatbuffers);
for (const eastl::vector<std::byte>& flatbuffer: flatbuffers)
{
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(0, m_uuids.size() - 1)]));
}
state.SetItemsProcessed(state.iterations());
state.SetBytesProcessed(static_cast<std::int64_t>(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<std::size_t>(state.range(0));
constexpr std::size_t BATCH_POOL_SIZE = 64;
const eastl::vector<eastl::vector<UUID>> 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<std::int64_t>(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<std::size_t>(0, m_uuids.size() - 1)]);
}
const eastl::vector<std::byte> flatbuffer = asset.Pack();
const eastl::vector<UUID> hardReferences = asset.GetHardReferences(flatbuffer);
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(state.range(0));
std::int64_t bytesProcessed = 0;
for (auto _: state)
{
state.PauseTiming();
eastl::vector<UUID> uuids;
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::size_t>(0, m_uuids.size() - 1)]);
}
eastl::vector<std::byte> flatbuffer = asset.Pack();
uuids.push_back(asset.GetHeader().uuid);
bytesProcessed += static_cast<std::int64_t>(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
+14
View File
@@ -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
$<LINK_LIBRARY:WHOLE_ARCHIVE,AssetsForBenchmarking>)
bigfoot_create_bigfile("Benchmarks/Bigfoot")
bigfoot_create_sasquatch_info()
@@ -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 <EASTL/string_view.h>
#if defined BIGFOOT_TOOLMODE
namespace Bigfoot
{
/*
* BigFile location
*/
constexpr eastl::string_view BIGFILE_ENGINEBENCHMARKS_LOCATION {"./EngineBenchmarks.db"};
} // namespace Bigfoot
#endif
#endif
@@ -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 <EASTL/string_view.h>
#if defined BIGFOOT_GAME
namespace Bigfoot
{
/*
* Sasquatch location
*/
constexpr eastl::string_view SASQUATCH_ENGINEBENCHMARKS_LOCATION {"./EngineBenchmarks.sasquatch"};
} // namespace Bigfoot
#endif
#endif
@@ -0,0 +1,245 @@
/*********************************************************************
* \file Sasquatch.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Sasquatch/Sasquatch.hpp>
#include <System/File/File.hpp>
#include <System/UUID/UUID.hpp>
#include <EngineBenchmarks/SasquatchInfo_generated.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp>
#include <EASTL/optional.h>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstddef>
#include <cstdint>
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<UUID> RandomBatch(const std::size_t p_batchSize) const
{
eastl::vector<UUID> batch;
batch.reserve(p_batchSize);
for (std::size_t i = 0; i < p_batchSize; ++i)
{
batch.push_back(m_uuids[Random::get<std::size_t>(0, m_uuids.size() - 1)]);
}
return batch;
}
[[nodiscard]]
eastl::vector<eastl::vector<UUID>> RandomBatchPool(const std::size_t p_batchSize,
const std::size_t p_poolSize) const
{
eastl::vector<eastl::vector<UUID>> 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<Sasquatch> m_sasquatch;
eastl::span<const UUID> m_uuids;
private:
static const eastl::vector<UUID>& BuildData()
{
static const eastl::vector<UUID> s_uuids = []()
{
File {SASQUATCH_ENGINEBENCHMARKS_LOCATION}.Delete();
eastl::vector<UUID> uuids;
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::byte> 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<std::size_t>(0, m_uuids.size() - 1)]));
}
state.SetItemsProcessed(state.iterations());
state.SetBytesProcessed(static_cast<std::int64_t>(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<std::size_t>(state.range(0));
constexpr std::size_t BATCH_POOL_SIZE = 64;
const eastl::vector<eastl::vector<UUID>> 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<std::int64_t>(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<std::byte> flatbuffer = m_sasquatch->GetAssetFlatbuffer(
m_uuids[Random::get<std::size_t>(0, m_uuids.size() - 1)]);
benchmark::DoNotOptimize(flatbuffer);
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(state.range(0));
constexpr std::size_t BATCH_POOL_SIZE = 64;
const eastl::vector<eastl::vector<UUID>> batchPool = RandomBatchPool(batchSize, BATCH_POOL_SIZE);
std::int64_t bytesProcessed = 0;
std::size_t batchIndex = 0;
for (auto _: state)
{
eastl::vector<eastl::vector<std::byte>> flatbuffers = m_sasquatch->GetAssetFlatbuffers(batchPool[batchIndex]);
benchmark::DoNotOptimize(flatbuffers);
for (const eastl::vector<std::byte>& flatbuffer: flatbuffers)
{
bytesProcessed += static_cast<std::int64_t>(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
@@ -0,0 +1,391 @@
/*********************************************************************
* \file SerializationManager.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/SerializationManager/SerializationManager.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <Engine/DataAccessor/DataAccessor.hpp>
#if defined BIGFOOT_TOOLMODE
#include <Engine/BigFile/BigFile.hpp>
#include <EngineBenchmarks/BigFileInfo_generated.hpp>
#elif defined BIGFOOT_GAME
#include <Engine/Sasquatch/Sasquatch.hpp>
#include <EngineBenchmarks/SasquatchInfo_generated.hpp>
#endif
#include <System/File/File.hpp>
#include <System/UUID/UUID.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAssetReferred.hpp>
#include <EASTL/optional.h>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstddef>
#include <cstdint>
#include <tuple>
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<BenchmarkAssetReferred>& p_leafContainer,
AssetContainer<BenchmarkAsset>& p_rootContainer)
{
std::ignore = p_library.Insert(&p_leafContainer);
std::ignore = p_library.Insert(&p_rootContainer);
SetActiveAssetLibrary(&p_library);
}
[[nodiscard]]
eastl::vector<UUID> RandomRootBatch(const std::size_t p_batchSize) const
{
eastl::vector<UUID> batch;
batch.reserve(p_batchSize);
for (std::size_t i = 0; i < p_batchSize; ++i)
{
batch.push_back(m_rootUuids[Random::get<std::size_t>(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<UUID>& p_roots) const
{
ankerl::unordered_dense::set<UUID> 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<std::int64_t>(m_rootTotalBytes->at(uuid));
}
return bytes;
}
DataAccessor* m_previousDataAccessor = nullptr;
AssetLibrary* m_previousAssetLibrary = nullptr;
eastl::span<const UUID> m_rootUuids;
eastl::span<const UUID> m_leafUuids;
const ankerl::unordered_dense::map<UUID, std::size_t>* m_rootTotalBytes = nullptr;
#if defined BIGFOOT_TOOLMODE
eastl::optional<BigFile> m_bigFile;
#elif defined BIGFOOT_GAME
eastl::optional<Sasquatch> m_sasquatch;
#endif
private:
struct SharedData
{
eastl::vector<UUID> rootUuids;
eastl::vector<UUID> leafUuids;
ankerl::unordered_dense::map<UUID, std::size_t> 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<UUID> uuids;
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::byte> 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<std::byte> 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<BenchmarkAssetReferred> leafContainer;
AssetContainer<BenchmarkAsset> rootContainer;
ResetActiveLibrary(library, leafContainer, rootContainer);
const UUID uuid = m_rootUuids[Random::get<std::size_t>(0, m_rootUuids.size() - 1)];
const std::size_t uuidBytes = m_rootTotalBytes->at(uuid);
state.ResumeTiming();
SerializationManager::LoadAsset(uuid);
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(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<BenchmarkAssetReferred> leafContainer;
AssetContainer<BenchmarkAsset> rootContainer;
ResetActiveLibrary(library, leafContainer, rootContainer);
const eastl::vector<UUID> 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<BenchmarkAsset> container;
std::ignore = library.Insert(&container);
SetActiveAssetLibrary(&library);
std::int64_t bytesProcessed = 0;
for (auto _: state)
{
state.PauseTiming();
auto* asset = CreateAsset<BenchmarkAsset>();
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<std::size_t>(0, m_leafUuids.size() - 1)]);
}
const UUID uuid = asset->GetHeader().uuid;
const std::size_t assetBytes = asset->Pack().size();
bytesProcessed += static_cast<std::int64_t>(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<std::size_t>(state.range(0));
AssetLibrary library;
AssetContainer<BenchmarkAsset> container;
std::ignore = library.Insert(&container);
SetActiveAssetLibrary(&library);
std::int64_t bytesProcessed = 0;
for (auto _: state)
{
state.PauseTiming();
eastl::vector<UUID> uuids;
uuids.reserve(batchSize);
std::size_t batchBytes = 0;
for (std::size_t i = 0; i < batchSize; ++i)
{
auto* asset = CreateAsset<BenchmarkAsset>();
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<std::size_t>(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<std::int64_t>(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
+1
View File
@@ -0,0 +1 @@
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/SasquatchBaker)
@@ -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
$<LINK_LIBRARY:WHOLE_ARCHIVE,AssetsForBenchmarking>)
bigfoot_create_bigfile("Benchmarks/Tools")
@@ -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 <EASTL/string_view.h>
#if defined BIGFOOT_TOOLMODE
namespace Bigfoot
{
/*
* BigFile location
*/
constexpr eastl::string_view BIGFILE_SASQUATCHBAKERBENCHMARKS_LOCATION {"./SasquatchBakerBenchmarks.db"};
} // namespace Bigfoot
#endif
#endif
@@ -0,0 +1,87 @@
/*********************************************************************
* \file SasquatchBaker.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <SasquatchBaker/SasquatchBaker.hpp>
#include <Engine/BigFile/BigFile.hpp>
#include <Engine/Sasquatch/Sasquatch.hpp>
#include <System/File/File.hpp>
#include <AssetsForBenchmarking/Asset/BenchmarkAsset.hpp>
#include <SasquatchBakerBenchmarks/BigFileInfo_generated.hpp>
#include <EASTL/vector.h>
#include <benchmark/benchmark.h>
#include <cstddef>
#include <cstdint>
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<UUID> uuids;
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::byte> 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<std::int64_t>(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
@@ -0,0 +1,92 @@
/*********************************************************************
* \file Graph.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Utils/Algorithm/Graph.hpp>
#include <ankerl/unordered_dense.h>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstdint>
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<std::uint32_t>(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<std::uint32_t>& 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<std::uint32_t> picked;
for (std::uint32_t i = 0; i < DEPENDENCY_MAX_COUNT; ++i)
{
const std::uint32_t dependency = Random::get<std::uint32_t>(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<std::uint32_t, eastl::vector<std::uint32_t>> m_graph;
ankerl::unordered_dense::set<std::uint32_t> m_nodes;
};
/****************************************************************************************/
BENCHMARK_DEFINE_F(LayeredTopologicalSortFixture, LayeredTopologicalSortBench)(benchmark::State& state)
{
const auto nodeCount = static_cast<std::uint32_t>(state.range(0));
for (auto _: state)
{
eastl::vector<eastl::vector<std::uint32_t>> waves;
eastl::vector<std::uint32_t> 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
+5
View File
@@ -0,0 +1,5 @@
get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(${PackageName}Benchmarks)
bigfoot_create_package_benchmarks(
"")
@@ -0,0 +1,259 @@
#include <Utils/Containers/SlotMap.hpp>
#include <ankerl/unordered_dense.h>
#include <benchmark/benchmark.h>
#include <effolkronium/random.hpp>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <vector>
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<MyComplexStruct>,
"Container moves should be a plain memcpy, not a heap-touching move.");
template<class SLOTMAP>
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<class FUNC>
void ForEachValue(FUNC&& p_func) const
{
for (const MyComplexStruct& value: m_container)
{
p_func(value);
}
}
private:
SLOTMAP m_container;
};
template<class MAP>
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<class FUNC>
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<class ADAPTOR>
void Insert(benchmark::State& state)
{
const auto count = static_cast<std::uint32_t>(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<std::int64_t>(state.iterations() * count * sizeof(MyComplexStruct)));
}
template<class ADAPTOR>
void Remove(benchmark::State& state)
{
const auto count = static_cast<std::uint32_t>(state.range(0));
ADAPTOR adaptor;
std::vector<typename ADAPTOR::Key> 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<std::int64_t>(state.iterations() * count * sizeof(MyComplexStruct)));
}
template<class ADAPTOR>
void Get(benchmark::State& state)
{
const auto count = static_cast<std::uint32_t>(state.range(0));
ADAPTOR adaptor;
std::vector<typename ADAPTOR::Key> 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<std::int64_t>(state.iterations() * count * sizeof(MyComplexStruct)));
}
template<class ADAPTOR>
void Iterate(benchmark::State& state)
{
const auto count = static_cast<std::uint32_t>(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<std::int64_t>(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<DenseSlotMap<MyComplexStruct>>;
using StableSlotMapHarness = SlotMapAdaptor<StableSlotMap<MyComplexStruct>>;
using UnorderedDenseMapHarness = HashMapHarnessAdaptor<ankerl::unordered_dense::map<std::uint64_t, MyComplexStruct>>;
using UnorderedDenseSegementedMapHarness =
HashMapHarnessAdaptor<ankerl::unordered_dense::segmented_map<std::uint64_t, MyComplexStruct>>;
using UnorderedMapHarness = HashMapHarnessAdaptor<std::unordered_map<std::uint64_t, MyComplexStruct>>;
using MapHarness = HashMapHarnessAdaptor<std::map<std::uint64_t, MyComplexStruct>>;
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
+4 -1
View File
@@ -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)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Engine)
if(${TOOLMODE})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Tools)
endif()
@@ -0,0 +1,62 @@
/*********************************************************************
* \file AssetBase.cpp
*
* \author Romain BOULLARD
* \date April 2026
*********************************************************************/
#include <Engine/Asset/AssetBase.hpp>
#include <Engine/Asset/Reference_generated.hpp>
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<UUID> AssetBase::HardReferenceVisitor::GetUUIDs() const
{
eastl::vector<UUID> 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<const ::Flat::Bigfoot::HardReference*>(p_val);
m_uuids.emplace(flatbuffers::UnPack(hardReference->uuid()));
}
} // namespace Bigfoot
@@ -0,0 +1,188 @@
/*********************************************************************
* \file AssetContainer.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Asset/AssetContainer.hpp>
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<UUID> 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
@@ -0,0 +1,86 @@
/*********************************************************************
* \file AssetLibrary.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Asset/AssetLibrary.hpp>
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
@@ -0,0 +1,24 @@
/*********************************************************************
* \file AssetTypeID.cpp
*
* \author Romain BOULLARD
* \date May 2026
*********************************************************************/
#include <Engine/Asset/AssetTypeID.hpp>
#include <Engine/Asset/AssetTypeID_generated.hpp>
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
+851
View File
@@ -0,0 +1,851 @@
/*********************************************************************
* \file BigFile.cpp
*
* \author Romain BOULLARD
* \date October 2025
*********************************************************************/
#include <Engine/BigFile/BigFile.hpp>
#include <Engine/EngineAssertHandler.hpp>
#include <Utils/Format.hpp>
#include <EASTL/algorithm.h>
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<std::byte> 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<const std::byte> flatbuffer = assetRequest.Get(0);
return eastl::vector<std::byte> {flatbuffer.begin(), flatbuffer.end()};
}
/****************************************************************************************/
eastl::vector<eastl::vector<std::byte>> BigFile::GetAssetFlatbuffers(const eastl::span<const UUID> p_uuids) const
{
eastl::vector<eastl::vector<std::byte>> flatbuffers(p_uuids.size());
if (p_uuids.empty())
{
return flatbuffers;
}
ankerl::unordered_dense::map<UUID, eastl::vector<std::size_t>> 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<UUID> 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<std::size_t>(
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<std::uint32_t>(i + 1), distinctUUIDs[chunkStart + i].ToBytes());
}
while (assetRequest.Step())
{
const std::span<const std::byte, UUID::UUID_BYTE_SIZE> rawUUID {
static_cast<std::span<const std::byte>>(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<const std::byte> flatbuffer = assetRequest.Get(1);
for (const std::size_t index: indicesIt->second)
{
flatbuffers[index].assign(flatbuffer.begin(), flatbuffer.end());
}
}
}
return flatbuffers;
}
/****************************************************************************************/
eastl::vector<UUID> 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<UUID> hardReferences;
while (hardReferencesRequest.Step())
{
const std::span<const std::byte, UUID::UUID_BYTE_SIZE> rawUUID {
static_cast<std::span<const std::byte>>(hardReferencesRequest.Get(0))};
hardReferences.emplace_back(rawUUID);
}
return hardReferences;
}
/****************************************************************************************/
eastl::vector<eastl::vector<UUID>> BigFile::GetAssetsHardReferences(eastl::span<const UUID> p_uuids) const
{
eastl::vector<eastl::vector<UUID>> hardReferences(p_uuids.size());
if (p_uuids.empty())
{
return hardReferences;
}
ankerl::unordered_dense::map<UUID, eastl::vector<std::size_t>> 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<UUID> 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<std::size_t>(
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<std::uint32_t>(i + 1), distinctUUIDs[chunkStart + i].ToBytes());
}
while (hardReferencesRequest.Step())
{
const std::span<const std::byte, UUID::UUID_BYTE_SIZE> rawOwnerUUID {
static_cast<std::span<const std::byte>>(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<const std::byte, UUID::UUID_BYTE_SIZE> rawHardReference {
static_cast<std::span<const std::byte>>(hardReferencesRequest.Get(1))};
for (const std::size_t index: indicesIt->second)
{
hardReferences[index].emplace_back(rawHardReference);
}
}
}
return hardReferences;
}
/****************************************************************************************/
eastl::optional<BigFile::AssetTime> 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<std::uint64_t>(timeRequest.Get(0))},
.m_modificationTime = Time {static_cast<std::uint64_t>(timeRequest.Get(1))}};
}
/****************************************************************************************/
eastl::vector<eastl::optional<BigFile::AssetTime>> BigFile::GetAssetsTime(const eastl::span<const UUID> p_uuids) const
{
eastl::vector<eastl::optional<AssetTime>> times(p_uuids.size());
if (p_uuids.empty())
{
return times;
}
ankerl::unordered_dense::map<UUID, eastl::vector<std::size_t>> 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<UUID> 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<std::size_t>(
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<std::uint32_t>(i + 1), distinctUUIDs[chunkStart + i].ToBytes());
}
while (timeRequest.Step())
{
const std::span<const std::byte, UUID::UUID_BYTE_SIZE> rawUUID {
static_cast<std::span<const std::byte>>(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<std::uint64_t>(timeRequest.Get(1))},
.m_modificationTime = Time {static_cast<std::uint64_t>(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<const std::byte> p_flatbuffer,
const eastl::span<const UUID> 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<std::size_t>(
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<const UUID> p_uuids,
const eastl::span<const eastl::vector<std::byte>> p_flatbuffers,
const eastl::span<const eastl::vector<UUID>> 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<std::size_t>(
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<std::uint32_t>(i + 1), p_uuids[chunkStart + i].ToBytes());
}
deleteHardReferencesRequest.Execute();
eastl::vector<eastl::pair<UUID, UUID>> 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<std::size_t>(
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<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_int(m_statement, static_cast<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_int(m_statement, static_cast<int>(p_index), static_cast<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_int64(m_statement, static_cast<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_int64(m_statement, static_cast<int>(p_index), std::bit_cast<std::int64_t>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_double(m_statement, static_cast<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_double(m_statement, static_cast<int>(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<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_text(m_statement,
static_cast<int>(p_index),
p_value.data(),
static_cast<int>(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<const std::byte> p_value,
const CopyValue p_copy)
{
ASSERT(EngineAssertHandler,
(p_index >= 1U) && (p_index <= static_cast<std::uint32_t>(sqlite3_bind_parameter_count(m_statement))),
"Invalid index for statement");
[[maybe_unused]]
const int result = sqlite3_bind_blob(m_statement,
static_cast<int>(p_index),
p_value.data(),
static_cast<int>(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<int>(m_index));
}
/****************************************************************************************/
BigFile::Request::Column::operator std::uint32_t() const
{
return sqlite3_column_int(m_statement, static_cast<int>(m_index));
}
/****************************************************************************************/
BigFile::Request::Column::operator std::int64_t() const
{
return sqlite3_column_int64(m_statement, static_cast<int>(m_index));
}
/****************************************************************************************/
BigFile::Request::Column::operator std::uint64_t() const
{
return std::bit_cast<std::uint64_t>(sqlite3_column_int64(m_statement, static_cast<int>(m_index)));
}
/****************************************************************************************/
BigFile::Request::Column::operator float() const
{
return static_cast<float>(sqlite3_column_double(m_statement, static_cast<int>(m_index)));
}
/****************************************************************************************/
BigFile::Request::Column::operator double() const
{
return sqlite3_column_double(m_statement, static_cast<int>(m_index));
}
/****************************************************************************************/
BigFile::Request::Column::operator eastl::string_view() const
{
return eastl::string_view {
reinterpret_cast<const char*>(sqlite3_column_text(m_statement, static_cast<int>(m_index))),
static_cast<std::size_t>(sqlite3_column_bytes(m_statement, static_cast<int>(m_index)))};
}
/****************************************************************************************/
BigFile::Request::Column::operator eastl::span<const std::byte>() const
{
return eastl::span<const std::byte> {
static_cast<const std::byte*>(sqlite3_column_blob(m_statement, static_cast<int>(m_index))),
static_cast<std::size_t>(sqlite3_column_bytes(m_statement, static_cast<int>(m_index)))};
}
/****************************************************************************************/
BigFile::Request::Column::operator std::span<const std::byte>() const
{
return std::span<const std::byte> {
static_cast<const std::byte*>(sqlite3_column_blob(m_statement, static_cast<int>(m_index))),
static_cast<std::size_t>(sqlite3_column_bytes(m_statement, static_cast<int>(m_index)))};
}
/****************************************************************************************/
BigFile::Request::Column BigFile::Request::Get(const std::uint32_t p_index) const
{
ASSERT(EngineAssertHandler,
p_index < static_cast<std::uint32_t>(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
@@ -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;
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);
+1 -2
View File
@@ -5,8 +5,7 @@ set(PublicDependencies
SQLite::SQLite3)
set(PrivateDependencies)
set(BigfootPublicDependencies
System
Utils)
System)
set(BigfootPrivateDependencies)
set(BIGFOOT_MAJOR 0)
@@ -0,0 +1,15 @@
/*********************************************************************
* \file DataAccessor.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/DataAccessor/DataAccessor.hpp>
namespace Bigfoot
{
void SetDataAccessor(DataAccessor* p_dataAccessor)
{
g_dataAccessor = p_dataAccessor;
}
} // namespace Bigfoot
@@ -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;
@@ -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 <Engine/Asset/AssetBase.hpp>
#include <Engine/Asset/AssetConcepts.hpp>
#include <Engine/Asset/AssetContainer.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <Engine/Asset/Asset_generated.hpp>
#include <Engine/EngineAssertHandler.hpp>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <flatbuffers/minireflect.h>
#include <rapidhash.h>
namespace Bigfoot
{
template<class ASSET>
requires AssetTraits<ASSET>
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<const std::byte> p_flatbuffer) override
{
#ifdef BIGFOOT_NOT_OPTIMIZED
flatbuffers::Verifier verifier {std::bit_cast<std::uint8_t*>(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<FLAT_ASSET>();
CRITICAL_ASSERT(EngineAssertHandler, innerAssetVerified, "Given asset could not be verified!");
#endif
asset->header()->UnPackTo(&m_header);
flatbuffers::GetRoot<FLAT_ASSET>(asset->inner_asset()->data())->UnPackTo(&m_asset, nullptr);
}
/**
* Pack the asset to a flatbuffer
*
* \return The packed asset
*/
[[nodiscard]]
eastl::vector<std::byte> 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::byte> {std::bit_cast<std::byte*>(assetFbb.GetBufferPointer()),
std::bit_cast<std::byte*>(assetFbb.GetBufferPointer() + assetFbb.GetSize())};
}
/**
* Get the HardReferences the flatbuffer has
*
* \param p_flatbuffer The flatbuffer
* \return The UUIDs of the HardReferences
*/
[[nodiscard]]
eastl::vector<UUID> GetHardReferences(const eastl::span<const std::byte> p_flatbuffer) const override
{
#ifdef BIGFOOT_NOT_OPTIMIZED
flatbuffers::Verifier verifier {std::bit_cast<std::uint8_t*>(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<FLAT_ASSET>();
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<class ASSET>
requires AssetType<ASSET>
[[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<ASSET*>(handle->GetAsset());
}
} // namespace Bigfoot
#endif
@@ -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 <Engine/Asset/Asset_generated.hpp>
#include <System/UUID/UUID.hpp>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <flatbuffers/minireflect.h>
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<UUID> GetHardReferences(const eastl::span<const std::byte> p_flatbuffer) const = 0;
/**
* Unpack a flatbuffer to the asset
*
* \param p_flatbuffer The flatbuffer to unpack
*/
virtual void Unpack(const eastl::span<const std::byte> p_flatbuffer) = 0;
/**
* Pack the asset to a flatbuffer
*
* \return The packed asset
*/
[[nodiscard]]
virtual eastl::vector<std::byte> 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<UUID> 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<UUID> m_uuids;
};
};
} // namespace Bigfoot
#endif
@@ -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 <Engine/Asset/AssetTypeID.hpp>
#include <System/UUID/UUID.hpp>
#include <flatbuffers/minireflect.h>
#include <concepts>
namespace Bigfoot
{
class AssetBase;
/**
* The traits struct passed to Asset<TRAITS> (e.g. AssetATraits).
* Must name the generated flatbuffers table type for the asset.
*/
template<class TRAITS>
concept AssetTraits = requires {
typename TRAITS::FLAT;
typename TRAITS::FLAT::NativeTableType;
{ TRAITS::FLAT::GetFullyQualifiedName() } -> std::convertible_to<const char*>;
{ TRAITS::FLAT::MiniReflectTypeTable() } -> std::convertible_to<const flatbuffers::TypeTable*>;
};
/**
* A concrete Asset type (e.g. AssetA), usable with AssetContainer, AssetLibrary and References.
*/
template<class ASSET>
concept AssetType = std::derived_from<ASSET, AssetBase> && !std::same_as<ASSET, AssetBase> &&
requires(const ASSET& p_asset) {
{ ASSET::GetTypeID() } -> std::convertible_to<AssetTypeID>;
{ p_asset.GetHeader().uuid } -> std::convertible_to<UUID>;
};
} // namespace Bigfoot
#endif
@@ -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 <Engine/Asset/AssetBase.hpp>
#include <Engine/Asset/AssetConcepts.hpp>
#include <Engine/EngineAssertHandler.hpp>
#include <Utils/Containers/SlotMap.hpp>
#include <ankerl/unordered_dense.h>
#include <cstdint>
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<ContainerHandle>;
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<const std::byte> 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<UUID, Entry> m_uuidToEntry;
/**
* Assets queued for removal
*/
eastl::vector<UUID> m_pendingRemovalUuids;
};
/****************************************************************************************/
template<class ASSET>
requires AssetType<ASSET>
class AssetContainer: public AssetContainerBase
{
using SlotMap = StableSlotMap<ASSET>;
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<const std::byte> 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<const std::byte>* 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
@@ -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 <Engine/Asset/AssetContainer.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <Engine/Asset/Reference.hpp>
#define BIGFOOT_ASSET(AssetName, AssetType) \
namespace Bigfoot \
{ \
template<> \
AssetTypeID GetAssetTypeID<AssetType>() \
{ \
return AssetType::GetTypeID(); \
} \
AssetContainer<AssetType> 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
@@ -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 <Engine/Asset/AssetConcepts.hpp>
#include <Engine/Asset/AssetContainer.hpp>
#include <Engine/Asset/AssetTypeID.hpp>
#include <ankerl/unordered_dense.h>
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<class ASSET>
requires AssetType<ASSET>
[[nodiscard]]
bool Insert(AssetContainer<ASSET>* 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<AssetTypeID, AssetContainerBase*> 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
@@ -0,0 +1,8 @@
native_include "Engine/Asset/AssetTypeID.hpp";
namespace Flat.Bigfoot;
struct AssetTypeID (native_type: "::Bigfoot::AssetTypeID")
{
value: ulong;
}
@@ -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 <cstdint>
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
@@ -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_
@@ -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<const Flat::Bigfoot::UUID *>(VT_UUID);
}
const ::flatbuffers::String *name() const {
return GetPointer<const ::flatbuffers::String *>(VT_NAME);
}
const Flat::Bigfoot::AssetTypeID *type_id() const {
return GetStruct<const Flat::Bigfoot::AssetTypeID *>(VT_TYPE_ID);
}
const ::flatbuffers::String *type_name() const {
return GetPointer<const ::flatbuffers::String *>(VT_TYPE_NAME);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
return VerifyTableStart(verifier) &&
VerifyFieldRequired<Flat::Bigfoot::UUID>(verifier, VT_UUID, 1) &&
VerifyOffsetRequired(verifier, VT_NAME) &&
verifier.VerifyString(name()) &&
VerifyFieldRequired<Flat::Bigfoot::AssetTypeID>(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<AssetHeader> 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<AssetHeader> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<AssetHeader>(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<AssetHeader> 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<AssetHeader> 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<Flat::Bigfoot::AssetHeaderT> header{new Flat::Bigfoot::AssetHeaderT()};
eastl::vector<uint8_t> 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<const Flat::Bigfoot::AssetHeader *>(VT_HEADER);
}
const ::flatbuffers::Vector<uint8_t> *inner_asset() const {
return GetPointer<const ::flatbuffers::Vector<uint8_t> *>(VT_INNER_ASSET);
}
template <bool B = false>
bool Verify(::flatbuffers::VerifierTemplate<B> &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<Asset> 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<Flat::Bigfoot::AssetHeader> header) {
fbb_.AddOffset(Asset::VT_HEADER, header);
}
void add_inner_asset(::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> inner_asset) {
fbb_.AddOffset(Asset::VT_INNER_ASSET, inner_asset);
}
explicit AssetBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
: fbb_(_fbb) {
start_ = fbb_.StartTable();
}
::flatbuffers::Offset<Asset> Finish() {
const auto end = fbb_.EndTable(start_);
auto o = ::flatbuffers::Offset<Asset>(end);
fbb_.Required(o, Asset::VT_HEADER);
fbb_.Required(o, Asset::VT_INNER_ASSET);
return o;
}
};
inline ::flatbuffers::Offset<Asset> CreateAsset(
::flatbuffers::FlatBufferBuilder &_fbb,
::flatbuffers::Offset<Flat::Bigfoot::AssetHeader> header = 0,
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> 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<Asset> 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<AssetHeaderT>();
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<AssetHeader> CreateAssetHeader(::flatbuffers::FlatBufferBuilder &_fbb, const AssetHeaderT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) {
return AssetHeader::Pack(_fbb, _o, _rehasher);
}
inline ::flatbuffers::Offset<AssetHeader> 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<AssetT>();
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<Flat::Bigfoot::AssetHeaderT>(_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<Asset> CreateAsset(::flatbuffers::FlatBufferBuilder &_fbb, const AssetT *_o, const ::flatbuffers::rehasher_function_t *_rehasher) {
return Asset::Pack(_fbb, _o, _rehasher);
}
inline ::flatbuffers::Offset<Asset> 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<Flat::Bigfoot::Asset>(buf);
}
inline const Flat::Bigfoot::Asset *GetSizePrefixedAsset(const void *buf) {
return ::flatbuffers::GetSizePrefixedRoot<Flat::Bigfoot::Asset>(buf);
}
template <bool B = false>
inline bool VerifyAssetBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifyBuffer<Flat::Bigfoot::Asset>(nullptr);
}
template <bool B = false>
inline bool VerifySizePrefixedAssetBuffer(
::flatbuffers::VerifierTemplate<B> &verifier) {
return verifier.template VerifySizePrefixedBuffer<Flat::Bigfoot::Asset>(nullptr);
}
inline const char *AssetExtension() {
return "bfbs";
}
inline void FinishAssetBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::Asset> root) {
fbb.Finish(root);
}
inline void FinishSizePrefixedAssetBuffer(
::flatbuffers::FlatBufferBuilder &fbb,
::flatbuffers::Offset<Flat::Bigfoot::Asset> root) {
fbb.FinishSizePrefixed(root);
}
inline eastl::unique_ptr<Flat::Bigfoot::AssetT> UnPackAsset(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::AssetT>(GetAsset(buf)->UnPack(res));
}
inline eastl::unique_ptr<Flat::Bigfoot::AssetT> UnPackSizePrefixedAsset(
const void *buf,
const ::flatbuffers::resolver_function_t *res = nullptr) {
return eastl::unique_ptr<Flat::Bigfoot::AssetT>(GetSizePrefixedAsset(buf)->UnPack(res));
}
} // namespace Bigfoot
} // namespace Flat
#endif // FLATBUFFERS_GENERATED_ASSET_FLAT_BIGFOOT_H_
@@ -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;
}
@@ -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 <Engine/Asset/AssetConcepts.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <System/UUID/UUID.hpp>
#include <utility>
namespace Bigfoot
{
template<class ASSET>
AssetTypeID GetAssetTypeID();
template<>
AssetTypeID GetAssetTypeID<AssetBase>() = delete;
template<class DERIVED, class ASSET>
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>, "ASSET must derive from AssetBase (and not be AssetBase itself)");
return m_handle ? static_cast<const ASSET*>(m_handle->GetAsset()) : nullptr;
}
/**
* Get the asset
*
* \return The asset, nullptr if it is not loaded
*/
[[nodiscard]]
ASSET* Get()
{
static_assert(AssetType<ASSET>, "ASSET must derive from AssetBase (and not be AssetBase itself)");
return m_handle ? static_cast<ASSET*>(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<ASSET>()))
{
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 ASSET>
class HardReference: public ReferenceBase<HardReference<ASSET>, ASSET>
{
using Base = ReferenceBase<HardReference<ASSET>, 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 ASSET>
class SoftReference: public ReferenceBase<SoftReference<ASSET>, ASSET>
{
using Base = ReferenceBase<SoftReference<ASSET>, 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
@@ -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_
@@ -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 <Engine/DataAccessor/DataAccessor.hpp>
#include <System/File/File.hpp>
#include <System/Time/Time.hpp>
#include <Utils/TaggedType.hpp>
#include <EASTL/optional.h>
#include <EASTL/span.h>
#include <EASTL/string_view.h>
#include <sqlite3.h>
#include <span>
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<std::byte> 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<eastl::vector<std::byte>> GetAssetFlatbuffers(eastl::span<const UUID> 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<UUID> 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<eastl::vector<UUID>> GetAssetsHardReferences(eastl::span<const UUID> 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<AssetTime> 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<eastl::optional<AssetTime>> GetAssetsTime(eastl::span<const UUID> 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<const std::byte> p_flatbuffer,
const eastl::span<const UUID> 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<const UUID> p_uuids,
const eastl::span<const eastl::vector<std::byte>> p_flatbuffers,
const eastl::span<const eastl::vector<UUID>> 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<bool>;
/*
* 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<const std::byte> 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 std::byte>() const;
/*
* Get value as a blob
*
* \return The value
*/
operator std::span<const std::byte>() 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
@@ -9,6 +9,8 @@
#include <EASTL/string_view.h>
#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
@@ -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 <Engine/Asset/AssetTypeID.hpp>
#include <System/Time/Time.hpp>
#include <System/UUID/UUID.hpp>
#include <EASTL/span.h>
#include <EASTL/string.h>
#include <EASTL/vector.h>
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<std::byte> 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<eastl::vector<std::byte>> GetAssetFlatbuffers(eastl::span<const UUID> 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<UUID> 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<eastl::vector<UUID>> GetAssetsHardReferences(eastl::span<const UUID> 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<const std::byte> p_flatbuffer,
const eastl::span<const UUID> 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<const UUID> p_uuids,
const eastl::span<const eastl::vector<std::byte>> p_flatbuffers,
const eastl::span<const eastl::vector<UUID>> 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
@@ -6,55 +6,15 @@
*********************************************************************/
#ifndef BIGFOOT_ENGINE_ENGINEASSERTHANDLER_HPP
#define BIGFOOT_ENGINE_ENGINEASSERTHANDLER_HPP
#include <Utils/Assert.hpp>
#include <Engine/EngineLogger_generated.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
#include <EASTL/utility.h>
#include <Utils/Assert/AssertHandler.hpp>
#include <format>
#include <source_location>
#include <string_view>
#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<typename... ARGS>
static void Handle(const std::source_location& p_location,
const std::string_view p_stacktrace,
std::format_string<ARGS...> p_format,
ARGS&&... p_args)
{
BIGFOOT_LOG_FATAL(ENGINE_LOGGER,
"Assert: {} (File:{}, Line:{}, Function:{}\n{}",
std::format(p_format, eastl::forward<ARGS>(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<ENGINE_LOGGER>;
} // namespace Bigfoot
#endif
#endif
@@ -6,7 +6,7 @@
*********************************************************************/
#ifndef BIGFOOT_ENGINELOGGER_GENERATED_HPP
#define BIGFOOT_ENGINELOGGER_GENERATED_HPP
#include <System/Log/Log.hpp>
#include <Utils/Log/LogMacros.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
@@ -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 <Engine/DataAccessor/DataAccessor.hpp>
#include <System/File/File.hpp>
#include <System/File/MemoryMappedFile.hpp>
#include <System/UUID/UUID.hpp>
#include <Utils/TargetMacros.h>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <cstddef>
#include <cstdint>
#include <type_traits>
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<SasquatchHeader>);
/****************************************************************************************/
struct SasquatchEntry
{
std::array<std::byte, UUID::UUID_BYTE_SIZE> 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<SasquatchEntry>);
/****************************************************************************************/
#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<const std::byte> p_flatbuffer,
eastl::span<const UUID> 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<SasquatchEntry> 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<std::byte> 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<eastl::vector<std::byte>> GetAssetFlatbuffers(eastl::span<const UUID> 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<UUID> 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<eastl::vector<UUID>> GetAssetsHardReferences(eastl::span<const UUID> p_uuids) const override;
/**
* Sasquatch is read-only: this will always fail
*
*/
void SaveAssetFlatbuffer(const UUID& p_uuid,
eastl::span<const std::byte> p_flatbuffer,
eastl::span<const UUID> p_hardReferences) override;
/**
* Sasquatch is read-only: this will always fail
*
*/
void SaveAssetFlatbuffers(const eastl::span<const UUID> p_uuids,
eastl::span<const eastl::vector<std::byte>> p_flatbuffers,
eastl::span<const eastl::vector<UUID>> 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<UUID, Entry> m_entries;
};
#endif
} // namespace Bigfoot
#endif
@@ -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 <EASTL/string_view.h>
#if defined BIGFOOT_GAME
namespace Bigfoot
{
/*
* Sasquatch location
*/
constexpr eastl::string_view SASQUATCH_@SASQUATCH_NAME@_LOCATION {"@SASQUATCH_LOCATION@"};
} // namespace Bigfoot
#endif
#endif
@@ -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 <System/UUID/UUID.hpp>
#include <EASTL/span.h>
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<const UUID> 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<const UUID> p_uuids);
#endif
} // namespace Bigfoot::SerializationManager
#endif
@@ -0,0 +1,266 @@
/*********************************************************************
* \file Sasquatch.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/Sasquatch/Sasquatch.hpp>
#include <Engine/EngineAssertHandler.hpp>
#include <algorithm>
#include <cstring>
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<const std::byte> p_flatbuffer,
eastl::span<const UUID> 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<std::byte> 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<const std::byte, UUID::UUID_BYTE_SIZE> rawUUID = p_hardReferences[i].ToBytes();
std::memcpy(data.data() + entry.m_hardReferencesOffset + (static_cast<std::size_t>(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<const std::byte> 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<SasquatchEntry> 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<std::byte> Sasquatch::GetAssetFlatbuffer(const UUID& p_uuid) const
{
const auto entryIt = m_entries.find(p_uuid);
if (entryIt == m_entries.end())
{
return {};
}
const eastl::span<const std::byte> data = m_file.GetData();
const eastl::span<const std::byte> bytes {data.data() + entryIt->second.m_assetOffset,
static_cast<std::size_t>(entryIt->second.m_assetSize)};
return eastl::vector<std::byte> {bytes.begin(), bytes.end()};
}
/****************************************************************************************/
eastl::vector<eastl::vector<std::byte>> Sasquatch::GetAssetFlatbuffers(const eastl::span<const UUID> p_uuids) const
{
eastl::vector<eastl::vector<std::byte>> flatbuffers;
flatbuffers.reserve(p_uuids.size());
for (const UUID& uuid: p_uuids)
{
flatbuffers.push_back(GetAssetFlatbuffer(uuid));
}
return flatbuffers;
}
/****************************************************************************************/
eastl::vector<UUID> Sasquatch::GetAssetHardReferences(const UUID& p_uuid) const
{
eastl::vector<UUID> hardReferences;
const auto entryIt = m_entries.find(p_uuid);
if (entryIt == m_entries.end())
{
return hardReferences;
}
const eastl::span<const std::byte> data = m_file.GetData();
const eastl::span<const std::byte> bytes {data.data() + entryIt->second.m_hardReferencesOffset,
static_cast<std::size_t>(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<std::byte, UUID::UUID_BYTE_SIZE> rawUUID;
std::memcpy(rawUUID.data(), bytes.data() + (i * UUID::UUID_BYTE_SIZE), UUID::UUID_BYTE_SIZE);
hardReferences.emplace_back(rawUUID);
}
return hardReferences;
}
/****************************************************************************************/
eastl::vector<eastl::vector<UUID>> Sasquatch::GetAssetsHardReferences(eastl::span<const UUID> p_uuids) const
{
eastl::vector<eastl::vector<UUID>> 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<const std::byte> p_flatbuffer,
[[maybe_unused]] eastl::span<const UUID> p_hardReferences)
{
ASSERT(EngineAssertHandler, false, "Sasquatch is read-only, it cannot save assets");
}
/****************************************************************************************/
void Sasquatch::SaveAssetFlatbuffers([[maybe_unused]] const eastl::span<const UUID> p_uuids,
[[maybe_unused]] eastl::span<const eastl::vector<std::byte>> p_flatbuffers,
[[maybe_unused]] eastl::span<const eastl::vector<UUID>> 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
@@ -0,0 +1,248 @@
/*********************************************************************
* \file SerializationManager.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <Engine/SerializationManager/SerializationManager.hpp>
#include <Engine/Asset/AssetLibrary.hpp>
#include <Engine/DataAccessor/DataAccessor.hpp>
#include <Utils/Algorithm/Graph.hpp>
#include <ankerl/unordered_dense.h>
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<UUID, eastl::vector<UUID>> BuildDependencyGraph(
const eastl::span<const UUID> p_roots,
ankerl::unordered_dense::set<UUID>& p_discovered)
{
ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph;
eastl::vector<UUID> 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<eastl::vector<UUID>> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery);
eastl::vector<UUID> nextToQuery;
for (std::size_t i = 0; i < toQuery.size(); ++i)
{
eastl::vector<UUID>& 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<const UUID> p_uuids)
{
ankerl::unordered_dense::set<UUID> discovered;
const ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> 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<eastl::vector<UUID>> waves;
eastl::vector<UUID> 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<UUID>& wave: waves)
{
const eastl::vector<eastl::vector<std::byte>> assetFlatbuffers = g_dataAccessor->GetAssetFlatbuffers(wave);
for (std::size_t i = 0; i < wave.size(); ++i)
{
const eastl::vector<std::byte>& 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<std::uint8_t*>(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<const UUID> {&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<UUID> FindCyclicDependencies(const eastl::span<const UUID> p_uuids,
const eastl::span<const eastl::vector<UUID>> p_hardReferences)
{
ankerl::unordered_dense::map<UUID, eastl::vector<UUID>> graph;
ankerl::unordered_dense::set<UUID> discovered;
eastl::vector<UUID> 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<UUID>& hardReferences: p_hardReferences)
{
for (const UUID& hardReference: hardReferences)
{
if (discovered.insert(hardReference).second)
{
toQuery.push_back(hardReference);
}
}
}
while (!toQuery.empty())
{
eastl::vector<eastl::vector<UUID>> hardReferencesBatch = g_dataAccessor->GetAssetsHardReferences(toQuery);
eastl::vector<UUID> nextToQuery;
for (std::size_t i = 0; i < toQuery.size(); ++i)
{
eastl::vector<UUID>& 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<eastl::vector<UUID>> waves;
eastl::vector<UUID> 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<std::byte> flatbuffer = asset->Pack();
const eastl::vector<UUID> hardReferences = asset->GetHardReferences(flatbuffer);
const eastl::vector<UUID> cyclicUuids = FindCyclicDependencies(
eastl::span<const UUID> {&p_uuid, 1},
eastl::span<const eastl::vector<UUID>> {&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<const UUID> p_uuids)
{
eastl::vector<eastl::vector<std::byte>> flatbuffers;
eastl::vector<eastl::vector<UUID>> 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<std::byte> flatbuffer = asset->Pack();
flatbuffers.push_back(flatbuffer);
hardReferences.push_back(asset->GetHardReferences(flatbuffer));
}
const eastl::vector<UUID> 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
-1
View File
@@ -1 +0,0 @@
// to delete when an actual source is in Engine
-10
View File
@@ -2,8 +2,6 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(${PackageName})
set(PublicDependencies
$<$<CONFIG:Debug,RelWithDebInfo>: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 $<$<CONFIG:Debug,RelWithDebInfo>:QUILL_NO_EXCEPTIONS>
PUBLIC $<$<CONFIG:Debug,RelWithDebInfo>: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)
-66
View File
@@ -1,66 +0,0 @@
/*********************************************************************
* \file File.cpp
*
* \author Romain BOULLARD
* \date December 2025
*********************************************************************/
#include <System/File.hpp>
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
+120
View File
@@ -0,0 +1,120 @@
/*********************************************************************
* \file File.cpp
*
* \author Romain BOULLARD
* \date December 2025
*********************************************************************/
#include <System/File/File.hpp>
#include <System/SystemAssertHandler.hpp>
#include <bit>
#include <fstream>
#include <string>
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::size_t>(std::filesystem::file_size(m_path));
}
/****************************************************************************************/
eastl::vector<std::byte> 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<std::streamoff>(p_offset), std::ios::beg);
eastl::vector<std::byte> data;
data.resize(p_size);
file.read(std::bit_cast<char*>(data.data()), static_cast<std::streamsize>(data.size()));
return data;
}
/****************************************************************************************/
void File::Write(const eastl::span<const std::byte> 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<std::streamoff>(p_offset), std::ios::beg);
file.write(std::bit_cast<const char*>(p_data.data()), static_cast<std::streamsize>(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
@@ -0,0 +1,234 @@
/*********************************************************************
* \file MemoryMappedFile.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <System/File/MemoryMappedFile.hpp>
#include <System/SystemAssertHandler.hpp>
#include <string>
#ifdef BIGFOOT_WINDOWS
#include <Windows.h>
#elif defined BIGFOOT_LINUX
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#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<std::size_t>(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<std::byte*>(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<std::size_t>(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<std::byte*>(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<const std::byte> MemoryMappedFile::GetData() const
{
return eastl::span<const std::byte> {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<LONGLONG>(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<std::byte*>(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<std::size_t>(fileStat.st_size) < p_size)
{
[[maybe_unused]]
const int truncateResult = ftruncate(m_fileDescriptor, static_cast<off_t>(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<std::byte*>(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<std::byte> WritableMemoryMappedFile::GetData() const
{
return eastl::span<std::byte> {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
@@ -7,8 +7,10 @@
#ifndef BIGFOOT_SYSTEM_FILE_HPP
#define BIGFOOT_SYSTEM_FILE_HPP
#include <EASTL/span.h>
#include <EASTL/string.h>
#include <EASTL/string_view.h>
#include <EASTL/vector.h>
#include <filesystem>
@@ -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<std::byte> 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<const std::byte> 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
@@ -0,0 +1,150 @@
/*********************************************************************
* \file MemoryMappedFile.hpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#ifndef BIGFOOT_SYSTEM_MEMORYMAPPEDFILE_HPP
#define BIGFOOT_SYSTEM_MEMORYMAPPEDFILE_HPP
#include <System/File/File.hpp>
#include <EASTL/span.h>
#include <cstddef>
#include <cstdint>
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<const std::byte> 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<std::byte> 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
@@ -1,95 +0,0 @@
/*********************************************************************
* \file EASTLFormatters.hpp
*
* \author Romain BOULLARD
* \date December 2025
*********************************************************************/
#ifndef BIGFOOT_SYSTEM_EASTLFORMATTERS_HPP
#define BIGFOOT_SYSTEM_EASTLFORMATTERS_HPP
#include <Utils/TargetMacros.h>
#if defined(BIGFOOT_NOT_OPTIMIZED)
#include <quill/DeferredFormatCodec.h>
#endif
#include <format>
#include <EASTL/string.h>
#include <EASTL/string_view.h>
// STRING
template<>
struct std::formatter<eastl::string>
{
constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
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<eastl::string>
{
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<eastl::string>: quill::DeferredFormatCodec<eastl::string>
{
};
#endif
// STRING_VIEW
template<>
struct std::formatter<eastl::string_view>
{
constexpr auto parse(std::format_parse_context& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
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<eastl::string_view>
{
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<eastl::string_view>: quill::DeferredFormatCodec<eastl::string_view>
{
};
#endif
#endif
@@ -1,182 +0,0 @@
/*********************************************************************
* \file Log.hpp
*
* \author Romain BOULLARD
* \date October 2025
*********************************************************************/
#ifndef BIGFOOT_SYSTEM_LOG_HPP
#define BIGFOOT_SYSTEM_LOG_HPP
#include <System/Log/EASTLFormatters.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
#include <System/Log/Log_generated.hpp>
#include <Utils/Singleton.hpp>
#include <EASTL/array.h>
#ifdef BIGFOOT_WINDOWS
#pragma warning(disable: 4702)
#endif
#include <quill/Backend.h>
#include <quill/Frontend.h>
#include <quill/LogMacros.h>
#include <quill/Logger.h>
#include <quill/sinks/ConsoleSink.h>
#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<std::shared_ptr<quill::Sink>, 1> m_sinks;
};
} // namespace Bigfoot
#define BIGFOOT_LOG_DEBUG(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_DEBUG(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_DEBUG(Bigfoot::Singleton<Bigfoot::Log>::Instance().RegisterLogger(loggerName), \
fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
} while (0)
#define BIGFOOT_LOG_TRACE(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_TRACE_L3(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_TRACE_L3(Bigfoot::Singleton<Bigfoot::Log>::Instance().RegisterLogger(loggerName), \
fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
} while (0)
#define BIGFOOT_LOG_INFO(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_INFO(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_INFO(Bigfoot::Singleton<Bigfoot::Log>::Instance().RegisterLogger(loggerName), \
fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
} while (0)
#define BIGFOOT_LOG_WARN(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_WARNING(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_WARNING(Bigfoot::Singleton<Bigfoot::Log>::Instance().RegisterLogger(loggerName), \
fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
} while (0)
#define BIGFOOT_LOG_ERROR(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_ERROR(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_ERROR(Bigfoot::Singleton<Bigfoot::Log>::Instance().RegisterLogger(loggerName), \
fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
} while (0)
#define BIGFOOT_LOG_FATAL(loggerName, fmt, ...) \
do \
{ \
if (quill::Logger* logger = Bigfoot::Singleton<Bigfoot::Log>::Instance().GetLogger(loggerName)) \
{ \
QUILL_LOG_CRITICAL(logger, fmt __VA_OPT__(, ) __VA_ARGS__); \
} \
else \
{ \
QUILL_LOG_CRITICAL(Bigfoot::Singleton<Bigfoot::Log>::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
@@ -6,55 +6,15 @@
*********************************************************************/
#ifndef BIGFOOT_SYSTEM_SYSTEMASSERTHANDLER_HPP
#define BIGFOOT_SYSTEM_SYSTEMASSERTHANDLER_HPP
#include <Utils/Assert.hpp>
#include <System/SystemLogger_generated.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
#include <EASTL/utility.h>
#include <Utils/Assert/AssertHandler.hpp>
#include <format>
#include <source_location>
#include <string_view>
#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<typename... ARGS>
static void Handle(const std::source_location& p_location,
const std::string_view p_stacktrace,
std::format_string<ARGS...> p_format,
ARGS&&... p_args)
{
BIGFOOT_LOG_FATAL(SYSTEM_LOGGER,
"Assert: {} (File:{}, Line:{}, Function:{}\n{}",
std::format(p_format, eastl::forward<ARGS>(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<SYSTEM_LOGGER>;
} // namespace Bigfoot
#endif
#endif
@@ -6,7 +6,7 @@
*********************************************************************/
#ifndef BIGFOOT_SYSTEMLOGGER_GENERATED_HPP
#define BIGFOOT_SYSTEMLOGGER_GENERATED_HPP
#include <System/Log/Log.hpp>
#include <Utils/Log/LogMacros.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
@@ -0,0 +1,8 @@
native_include "System/Time/Time.hpp";
namespace Flat.Bigfoot;
struct Time (native_type: "::Bigfoot::Time")
{
epoch:ulong;
}
@@ -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 <chrono>
#include <compare>
#include <cstdint>
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
@@ -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_
@@ -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
@@ -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")
{
@@ -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 <EASTL/string.h>
#include <ankerl/unordered_dense.h>
#include <fmt/format.h>
#include <uuid.h>
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<const std::byte, UUID_BYTE_SIZE> 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<const std::byte, UUID_BYTE_SIZE> 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 std::byte, UUID_BYTE_SIZE>() 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<UUID>;
/**
* The uuid.
*/
@@ -97,27 +106,34 @@ class UUID
template<>
struct std::hash<Bigfoot::UUID>
{
using is_avalanching = void;
[[nodiscard]]
std::size_t operator()(const Bigfoot::UUID& p_uuid) const noexcept
{
return std::hash<uuids::uuid>()(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<Bigfoot::UUID, char>
struct fmt::formatter<Bigfoot::UUID>
{
constexpr auto parse(std::format_parse_context& ctx)
static constexpr auto parse(format_parse_context& ctx)
{
return ctx.begin();
}
template<typename FormatContext>
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<std::string>(p_uuid));
return fmt::format_to(ctx.out(), "{}", p_uuid.ToString().c_str());
}
};
/****************************************************************************************/
#if defined BIGFOOT_NOT_OPTIMIZED
#include <quill/DeferredFormatCodec.h>
@@ -131,7 +147,7 @@ struct fmtquill::formatter<Bigfoot::UUID>
auto format(const Bigfoot::UUID& p_uuid, format_context& ctx) const
{
return fmtquill::format_to(ctx.out(), "{}", static_cast<std::string>(p_uuid));
return fmtquill::format_to(ctx.out(), "{}", p_uuid.ToString().c_str());
}
};
@@ -141,4 +157,19 @@ struct quill::Codec<Bigfoot::UUID>: quill::DeferredFormatCodec<Bigfoot::UUID>
};
#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
@@ -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_
@@ -4,9 +4,10 @@
* \author Romain BOULLARD
* \date December 2025
*********************************************************************/
#include <System/Time.hpp>
#include <System/Time/Time.hpp>
#include <System/SystemAssertHandler.hpp>
#include <System/Time/Time_generated.hpp>
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<std::uint32_t>(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
-27
View File
@@ -1,27 +0,0 @@
/*********************************************************************
* \file FlatUUID.cpp
*
* \author Romain BOULLARD
* \date December 2025
*********************************************************************/
#include <System/UUID/FlatUUID.hpp>
namespace flatbuffers
{
Bigfoot::Flat::UUID Pack(const Bigfoot::UUID& p_uuid)
{
const std::span<const std::byte, Bigfoot::UUID::UUID_BYTE_SIZE> bytes = p_uuid;
return Bigfoot::Flat::UUID {
std::span<const std::uint8_t, Bigfoot::UUID::UUID_BYTE_SIZE> {reinterpret_cast<const std::uint8_t*>(bytes.data()),
bytes.size()}};
}
/****************************************************************************************/
Bigfoot::UUID UnPack(const Bigfoot::Flat::UUID& p_flatUUID)
{
const std::span<const std::uint8_t, Bigfoot::UUID::UUID_BYTE_SIZE> bytesView {p_flatUUID.bytes()->data(),
p_flatUUID.bytes()->size()};
return Bigfoot::UUID {std::as_bytes(bytesView)};
}
} // namespace flatbuffers
+33 -3
View File
@@ -6,6 +6,8 @@
*********************************************************************/
#include <System/UUID/UUID.hpp>
#include <System/UUID/UUID_generated.hpp>
namespace Bigfoot
{
UUID UUID::NULL_UUID {"00000000-0000-0000-0000-000000000000"};
@@ -41,16 +43,17 @@ UUID::UUID(const std::span<const std::byte, UUID_BYTE_SIZE> p_raw)
/****************************************************************************************/
UUID::operator std::span<const std::byte, UUID::UUID_BYTE_SIZE>() const
std::span<const std::byte, UUID::UUID_BYTE_SIZE> 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<const std::byte, Bigfoot::UUID::UUID_BYTE_SIZE> bytes = p_uuid.ToBytes();
return Flat::Bigfoot::UUID {
std::span<const std::uint8_t, Bigfoot::UUID::UUID_BYTE_SIZE> {reinterpret_cast<const std::uint8_t*>(bytes.data()),
bytes.size()}};
}
/****************************************************************************************/
Bigfoot::UUID UnPack(const Flat::Bigfoot::UUID& p_flatUUID)
{
const std::span<const std::uint8_t, Bigfoot::UUID::UUID_BYTE_SIZE> bytesView {p_flatUUID.bytes()->data(),
p_flatUUID.bytes()->size()};
return Bigfoot::UUID {std::as_bytes(bytesView)};
}
} // namespace flatbuffers
+2
View File
@@ -0,0 +1,2 @@
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/SasquatchBaker)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Executables/SasquatchBaker)
@@ -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
$<LINK_LIBRARY:WHOLE_ARCHIVE,SasquatchBaker>)
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)
@@ -0,0 +1,50 @@
/*********************************************************************
* \file main.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <System/File/File.hpp>
#include <Utils/Log/LogMacros.hpp>
#include <Utils/TargetMacros.h>
#include <SasquatchBaker/SasquatchBaker.hpp>
#include <EASTL/string_view.h>
#include <lyra/lyra.hpp>
#include <iostream>
#include <string>
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;
}
@@ -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()
@@ -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 <Engine/BigFile/BigFile.hpp>
#include <System/File/File.hpp>
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
@@ -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 <Utils/Assert/AssertHandler.hpp>
#include <SasquatchBaker/SasquatchBakerLogger_generated.hpp>
#ifdef BIGFOOT_NOT_OPTIMIZED
namespace Bigfoot
{
using SasquatchBakerAssertHandler = AssertHandler<SASQUATCHBAKER_LOGGER>;
} // namespace Bigfoot
#endif
#endif
@@ -0,0 +1,21 @@
// AUTO-GENERATED DO NOT TOUCH
/*********************************************************************
* \file SasquatchBakerLogger.generated.hpp
*
*********************************************************************/
#ifndef BIGFOOT_SASQUATCHBAKERLOGGER_GENERATED_HPP
#define BIGFOOT_SASQUATCHBAKERLOGGER_GENERATED_HPP
#include <Utils/Log/LogMacros.hpp>
#if defined BIGFOOT_NOT_OPTIMIZED
namespace Bigfoot
{
/*
* Logger
*/
inline Log::LoggerInfo SASQUATCHBAKER_LOGGER {"SASQUATCHBAKER_LOGGER", Flat::LogLevel::Trace};
} // namespace Bigfoot
#endif
#endif
@@ -0,0 +1,65 @@
/*********************************************************************
* \file SasquatchBaker.cpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#include <SasquatchBaker/SasquatchBaker.hpp>
#include <Engine/Sasquatch/Sasquatch.hpp>
#include <SasquatchBaker/SasquatchBakerAssertHandler.hpp>
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<const std::byte> 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<const std::byte, UUID::UUID_BYTE_SIZE> {rawUUID.data(), UUID::UUID_BYTE_SIZE}};
const eastl::vector<std::byte> flatbuffer = p_bigFile.GetAssetFlatbuffer(uuid);
const eastl::vector<UUID> hardReferences = p_bigFile.GetAssetHardReferences(uuid);
writer.WriteAsset(uuid, flatbuffer, hardReferences);
}
}
} // namespace Bigfoot
+19 -2
View File
@@ -2,7 +2,15 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(${PackageName})
set(PublicDependencies
$<$<CONFIG:Debug,RelWithDebInfo>:cpptrace::cpptrace>)
$<$<CONFIG:Debug,RelWithDebInfo>:quill::quill>
$<IF:$<BOOL:${ASAN}>,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}"
"")
"")
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
$<$<CONFIG:Debug,RelWithDebInfo>:QUILL_NO_EXCEPTIONS>
$<$<CONFIG:Debug,RelWithDebInfo>:QUILL_DISABLE_NON_PREFIXED_MACROS>)
@@ -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 <Utils/UtilsAssertHandler.hpp>
#include <EASTL/vector.h>
#include <ankerl/unordered_dense.h>
#include <cstdint>
#include <limits>
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<class NODE_TYPE>
[[nodiscard]]
bool LayeredTopologicalSort(const ankerl::unordered_dense::map<NODE_TYPE, eastl::vector<NODE_TYPE>>& p_graph,
const ankerl::unordered_dense::set<NODE_TYPE>& p_nodes,
eastl::vector<eastl::vector<NODE_TYPE>>& p_waves,
eastl::vector<NODE_TYPE>& 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<std::uint32_t>::max(),
"Can't sort more than {} nodes",
std::numeric_limits<std::uint32_t>::max());
const auto nodeCount = static_cast<std::uint32_t>(p_nodes.size());
ankerl::unordered_dense::map<NODE_TYPE, std::uint32_t> nodeToIndex;
nodeToIndex.reserve(nodeCount);
eastl::vector<NODE_TYPE> indexToNode;
indexToNode.reserve(nodeCount);
for (const NODE_TYPE& node: p_nodes)
{
nodeToIndex.emplace(node, static_cast<std::uint32_t>(indexToNode.size()));
indexToNode.push_back(node);
}
// Step 1: Compute in-degree for each node
eastl::vector<std::uint32_t> 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<std::uint32_t> adjacencyOffsets(nodeCount + 1, 0);
// Initialize queue with nodes having in-degree 0
eastl::vector<std::uint32_t> 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<std::uint32_t> adjacency(adjacencyOffsets.back());
eastl::vector<std::uint32_t> 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<std::uint32_t> 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<NODE_TYPE> 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
@@ -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 <cpptrace/cpptrace.hpp>
#include <source_location>
#include <string>
#if defined BIGFOOT_LINUX
#include <csignal>
#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
@@ -0,0 +1,112 @@
/*********************************************************************
* \file Assert.hpp
*
* \author Romain BOULLARD
* \date October 2025
*********************************************************************/
#ifndef BIGFOOT_UTILS_ASSERT_HPP
#define BIGFOOT_UTILS_ASSERT_HPP
#include <Utils/Log/LogMacros.hpp>
#include <Utils/TargetMacros.h>
#ifdef BIGFOOT_NOT_OPTIMIZED
#include <Utils/Format.hpp>
#include <EASTL/fixed_function.h>
#include <EASTL/string.h>
#include <cstdlib>
#include <source_location>
#ifdef BIGFOOT_LINUX
#include <csignal>
#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<typename HANDLER>
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<typename HANDLER>
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<HANDLER>((p_assert), \
[&] \
{ \
return ::Bigfoot::Format(p_message __VA_OPT__(, ) __VA_ARGS__); \
})
#define SOFT_ASSERT(HANDLER, p_assert, p_message, ...) \
::Bigfoot::AssertCheck<HANDLER>((p_assert), \
[&] \
{ \
return ::Bigfoot::Format(p_message __VA_OPT__(, ) __VA_ARGS__); \
})
#define CRITICAL_ASSERT(HANDLER, p_assert, p_message, ...) \
::Bigfoot::CriticalAssertCheck<HANDLER>((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
@@ -0,0 +1,52 @@
/*********************************************************************
* \file AssertHandler.hpp
*
* \author Romain BOULLARD
* \date August 2026
*********************************************************************/
#ifndef BIGFOOT_UTILS_ASSERTHANDLER_HPP
#define BIGFOOT_UTILS_ASSERTHANDLER_HPP
#include <Utils/Assert/Assert.hpp>
#include <Utils/Log/Log.hpp>
#ifdef BIGFOOT_NOT_OPTIMIZED
#include <EASTL/string.h>
#include <source_location>
namespace Bigfoot
{
template<Log::LoggerInfo& LOGGER>
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
/*********************************************************************
* \file Format.hpp
*
* \author Romain BOULLARD
* \date September 2026
*********************************************************************/
#ifndef BIGFOOT_UTILS_FORMAT_HPP
#define BIGFOOT_UTILS_FORMAT_HPP
#include <EASTL/string.h>
#include <fmt/format.h>
#include <iterator>
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<typename... ARGS>
eastl::string Format(fmt::format_string<ARGS...> p_format, ARGS&&... p_args)
{
fmt::memory_buffer buffer;
fmt::format_to(std::back_inserter(buffer), p_format, std::forward<ARGS>(p_args)...);
return eastl::string {buffer.data(), buffer.size()};
}
} // namespace Bigfoot
#endif

Some files were not shown because too many files have changed in this diff Show More