From b640181797d614bcb11db10632594761428c0df3 Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 12 Feb 2024 20:46:03 +0100 Subject: [PATCH 1/6] move simple dependencies to CPM --- CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebda3800..e3aa2362 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,12 +10,11 @@ include(cmake/stacktrace.cmake) include(cmake/setup_cpm.cmake) conan_cmake_configure(REQUIRES - catch2/3.4.0 sdl/2.28.3 sdl_image/2.6.3 sdl_ttf/2.20.2 - fmt/10.1.1 - entt/3.12.2 + #fmt/10.1.1 + #entt/3.12.2 libpng/1.6.42 #resolve conflicts GENERATORS CMakeDeps CMakeToolchain ) @@ -25,15 +24,47 @@ conan_cmake_install(PATH_OR_REFERENCE . BUILD missing REMOTE conancenter SETTINGS ${settings}) +CPMAddPackage( + NAME fmt + GITHUB_REPOSITORY "fmtlib/fmt" + GIT_TAG 10.2.1 +) +CPMAddPackage( + NAME EnTT + GITHUB_REPOSITORY "skypjack/entt" + GIT_TAG v3.13.1 +) CPMAddPackage( - NAME sdlpp + NAME sdlpp GITHUB_REPOSITORY "mika314/sdlpp" GIT_TAG HEAD #OPTIONS USE_SDLGFX ) + +CPMAddPackage( + NAME cereal + + GITHUB_REPOSITORY "USCiLab/cereal" + GIT_TAG HEAD + OPTIONS "SKIP_PERFORMANCE_COMPARISON ON" "BUILD_SANDBOX OFF" +) + +CPMAddPackage( + NAME Catch2 + + GITHUB_REPOSITORY "catchorg/Catch2" + GIT_TAG v3.5.2 + OPTIONS + "CATCH_BUILD_TESTING OFF" + "CATCH_BUILD_EXAMPLES OFF" + "CATCH_BUILD_EXTRA_TESTS OFF" + "CATCH_BUILD_FUZZERS OFF" +) + + enable_testing() add_subdirectory(libs) add_subdirectory(apps) From 5c0e3799ae6108a215ce006966011fafc69680a8 Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 12 Feb 2024 20:46:31 +0100 Subject: [PATCH 2/6] minimal serialization sandbox --- apps/CMakeLists.txt | 1 + apps/serialization/CMakeLists.txt | 29 ++++ apps/serialization/main.cpp | 235 ++++++++++++++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 apps/serialization/CMakeLists.txt create mode 100644 apps/serialization/main.cpp diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 0505bf93..54913934 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(asteroids) add_subdirectory(textdemo) add_subdirectory(stellarfield) add_subdirectory(galaxy) +add_subdirectory(serialization) diff --git a/apps/serialization/CMakeLists.txt b/apps/serialization/CMakeLists.txt new file mode 100644 index 00000000..27098116 --- /dev/null +++ b/apps/serialization/CMakeLists.txt @@ -0,0 +1,29 @@ +project(serialization) + +set(CMAKE_CXX_STANDARD 23) +file(GLOB_RECURSE HEADER_FILES CONFIGURE_DEPENDS "*.h*") +file(GLOB_RECURSE CPP_FILES CONFIGURE_DEPENDS "*.cpp") + + +add_executable(${PROJECT_NAME} ${HEADER_FILES} ${CPP_FILES} ) + +find_package(fmt REQUIRED) +find_package(cereal REQUIRED) +find_package(EnTT REQUIRED) + +target_link_libraries(${PROJECT_NAME} + PUBLIC + pgEngine::pgEngine + fmt::fmt + cereal::cereal + EnTT::EnTT +) +target_include_directories(${PROJECT_NAME} + PRIVATE + $ + $ +) + +enable_coverage(${PROJECT_NAME}) + +install(TARGETS ${PROJECT_NAME}) \ No newline at end of file diff --git a/apps/serialization/main.cpp b/apps/serialization/main.cpp new file mode 100644 index 00000000..eb6f855d --- /dev/null +++ b/apps/serialization/main.cpp @@ -0,0 +1,235 @@ +#include +#include + +#include +#include +#include +#include +#include + +struct IdMetaAny +{ + entt::id_type id; + entt::meta_any any; + + template + void save(Ar& ar) const + { + ar(id); + any.invoke(entt::hashed_string("save"), entt::forward_as_meta(ar)); + } + + template + void load(Ar& ar) + { + ar(id); + any = entt::resolve(id).construct(); + any.invoke(entt::hashed_string("load"), entt::forward_as_meta(ar)); + } +}; + +template +void move_emplace(Component& elem, entt::sparse_set& storage, entt::entity entity) +{ + static_cast&>(storage).emplace(entity, std::move(elem)); +} + +template +void RegisterComponentForSerialize() +{ + using namespace entt::literals; + auto&& f = entt::meta().template func<&move_emplace>("emplace"_hs); + f = f.type(entt::type_id().hash()); + f = f.template func<&Component::template save>(entt::hashed_string("save")); + f = f.template func<&Component::template load>(entt::hashed_string("load")); +} + +struct Entity +{ + Entity(entt::registry* in_registry) + : registry{in_registry} + { + if (this->registry) { this->handle = this->registry->create(); } + } + + template + Comp& add(Args&&... args) + { + return this->registry->emplace(this->handle, args...); + } + + template + Comp* get() + { + return this->registry->try_get(this->handle); + } + + template + void remove() + { + this->registry->remove(this->handle); + } + + template + void save(Ar& ar) const + { + std::map components; + + for (auto&& [id, storage] : this->registry->storage()) + { + if (!storage.contains(this->handle)) { continue; } + + if (auto type = entt::resolve(id); type) + { + components.emplace(id, IdMetaAny{id, type.from_void(storage.value(this->handle))}); + } + } + ar(components); + } + + template + void load(Ar& ar) + { + using namespace entt::literals; + + std::map components; + ar(components); + + for (auto&& [id, storage] : this->registry->storage()) + { + if (auto itr = components.find(id); itr == components.end()) + { + // undo for add component. + storage.remove(this->handle); + } + else if (storage.contains(this->handle)) + { + // undo for update component + auto& any = itr->second.any; + storage.remove(this->handle); + any.type().invoke("emplace"_hs, any, entt::forward_as_meta(storage), this->handle); + } + } + + for (const auto& [id, id_any] : components) + { + if (auto storage = this->registry->storage(id); !storage->contains(this->handle)) + { + // undo for remove component + id_any.any.type().invoke("emplace"_hs, id_any.any, entt::forward_as_meta(storage), this->handle); + } + } + } + +private: + entt::registry* registry = nullptr; + entt::entity handle{entt::null}; +}; + +struct Position +{ + double x, y, z; + + template + void save(Ar& ar) const + { + ar(x, y, z); + } + + template + void load(Ar& ar) + { + ar(x, y, z); + } +}; + +bool trivially_copyable_case() +{ + entt::registry registry; + Entity e{®istry}; + + // initialize + e.add(1.0, 2.0, 3.0); + + // back up + std::stringstream ss; + { + cereal::BinaryOutputArchive ar(ss); + ar(e); + } + // update + { + auto comp = e.get(); + comp->x = 10; + comp->y = 20; + comp->z = 30; + } + // undo + { + cereal::BinaryInputArchive ar(ss); + ar(e); + } + // check + { + auto comp = e.get(); + return (comp != nullptr) && (comp->x == 1.0) && (comp->y == 2.0) && (comp->z == 3.0); + } +} + +struct Data +{ + std::unique_ptr value; + + template + void save(Ar& ar) const + { + ar(value); + } + + template + void load(Ar& ar) + { + ar(value); + } +}; + +bool unique_ptr_case() +{ + entt::registry registry; + Entity e{®istry}; + + // initialize + auto& comp = e.add(); + comp.value = std::make_unique(10.0); + + // back up + std::stringstream ss; + { + cereal::BinaryOutputArchive ar(ss); + ar(e); + } + // update + { + auto comp = e.get(); + comp->value = nullptr; + } + // undo + { + cereal::BinaryInputArchive ar(ss); + ar(e); + } + // check + { + auto comp = e.get(); + return (comp != nullptr) && (comp->value != nullptr) && (*(comp->value) == 10.0); + } +} + +int main() +{ + RegisterComponentForSerialize(); + RegisterComponentForSerialize(); + + assert(trivially_copyable_case() && L"fail to trivially_copyable_case"); + assert(unique_ptr_case() && L"fail to unique_ptr_case"); +} \ No newline at end of file From 610dd91d612f352c9ed710e24254747f408ff597 Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 12 Feb 2024 21:00:04 +0100 Subject: [PATCH 3/6] fix catch2 cmake setup --- tests/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aba666e9..28715756 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,12 @@ project(unittests LANGUAGES CXX) -find_package(Catch2 CONFIG REQUIRED) -include(CTest) -include(Catch) +find_package(Catch2 REQUIRED) + # Tests add_library(testMain OBJECT testMain.cpp) target_link_libraries(testMain PUBLIC Catch2::Catch2) - +list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) +include(CTest) +include(Catch) if(BUILD_TESTING) From be50e526a8b83ff2fde5d6d70b7865f122cb49e5 Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 12 Feb 2024 23:42:33 +0100 Subject: [PATCH 4/6] experimental save&load --- apps/serialization/{main.cpp => main._cpp} | 34 +++++++++++++- apps/serialization/main2.cpp | 54 ++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) rename apps/serialization/{main.cpp => main._cpp} (88%) create mode 100644 apps/serialization/main2.cpp diff --git a/apps/serialization/main.cpp b/apps/serialization/main._cpp similarity index 88% rename from apps/serialization/main.cpp rename to apps/serialization/main._cpp index eb6f855d..a3d2baea 100644 --- a/apps/serialization/main.cpp +++ b/apps/serialization/main._cpp @@ -225,11 +225,43 @@ bool unique_ptr_case() } } +bool simple() +{ + entt::registry registry; + auto entity = registry.create(); + + // initialize + auto d = Data{std::make_unique(10.0)}; + registry.emplace(entity, std::move(d)); + + // back up + std::stringstream ss; + { + cereal::BinaryOutputArchive ar(ss); + ar(entity); + } + // update + { + auto comp = registry.try_get(entity); + comp->value = nullptr; + } + // undo + { + cereal::BinaryInputArchive ar(ss); + ar(entity); + } + // check + { + auto comp = registry.try_get(entity); + return (comp != nullptr) && (comp->value != nullptr) && (*(comp->value) == 10.0); + } +} + int main() { RegisterComponentForSerialize(); RegisterComponentForSerialize(); - + simple(); assert(trivially_copyable_case() && L"fail to trivially_copyable_case"); assert(unique_ptr_case() && L"fail to unique_ptr_case"); } \ No newline at end of file diff --git a/apps/serialization/main2.cpp b/apps/serialization/main2.cpp new file mode 100644 index 00000000..22a406f0 --- /dev/null +++ b/apps/serialization/main2.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include +#include +#include +#include +#include + +struct Position +{ + int x; + int y; +}; + +template +void serialize(Archive& archive, Position& position) +{ + archive(position.x, position.y); +} + +void saveRegistry(entt::registry& reg, std::string_view file_name) +{ + std::ofstream storage("../data/test.out"); + + cereal::JSONOutputArchive output{storage}; + + entt::snapshot{reg}.get(output).get(output); +} + +void loadRegistry(entt::registry& reg, std::string_view file_name) +{ + std::ifstream storage("../data/test.out"); + + cereal::JSONInputArchive output{storage}; + + entt::basic_snapshot_loader{reg}.get(output).get(output); +} + +int main() +{ + entt::registry registry; + auto entity = registry.create(); + registry.emplace(entity, Position{42, 42}); + // registry.add(registry.create(), {3, 14}); + saveRegistry(registry, "data/test.out"); + { + auto& pos = registry.get(entity); + pos.x = 0; + } + registry.clear(); + loadRegistry(registry, "data/test.out"); + auto& pos2 = registry.get(entity); +} \ No newline at end of file From ed5d008f2f95e2e61aef7615c2de1e6e33f7767c Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 12 Feb 2024 23:45:58 +0100 Subject: [PATCH 5/6] caching cpm add chaching to env setup --- .github/workflows/cmake.yml | 29 +++++++++++++++++++++++++++++ doc/setup_env.sh | 2 ++ 2 files changed, 31 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 5143e587..57ad2364 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -22,6 +22,35 @@ jobs: os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: + - name: Cache CMake dependency source code + uses: actions/cache@v2 + env: + cache-name: cache-cmake-dependency-sources + with: + # CMake cache is at ${{github.workspace}}/build/_deps but we only will cache folders ending in '-src' to cache source code + path: ${{github.workspace}}/build/_deps/*-src + # Cache hash is dependent on CMakeLists files anywhere as these can change what's in the cache, as well as cmake modules files + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + # it's acceptable to reuse caches for different CMakeLists content if exact match is not available and unlike build caches, we + # don't need to restrict these by OS or compiler as it's only source code that's being cached + restore-keys: | + ${{ env.cache-name }}- + + - name: Cache CMake dependency build objects + uses: actions/cache@v2 + env: + cache-name: cache-cmake-dependency-builds + with: + # CMake cache is at ${{github.workspace}}/build/_deps but we only care about the folders ending in -build or -subbuild + path: | + ${{github.workspace}}/build/_deps/*-build + ${{github.workspace}}/build/_deps/*-subbuild + # Cache hash is dependent on CMakeLists files anywhere as these can change what's in the cache, as well as cmake modules files + key: ${{ env.cache-name }}-${{ matrix.os }}-${{ matrix.cxx }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + # it's acceptable to reuse caches for different CMakeLists content if exact match is not available + # as long as the OS and Compiler match exactly + restore-keys: | + ${{ env.cache-name }}-${{ matrix.os }}-${{ matrix.cxx }}- - name: Using the builtin GitHub Cache Action for .conan id: github-cache-conan uses: actions/cache@v2 diff --git a/doc/setup_env.sh b/doc/setup_env.sh index b700c214..5139ba2b 100644 --- a/doc/setup_env.sh +++ b/doc/setup_env.sh @@ -14,3 +14,5 @@ sudo -Hiu $USER env conan profile new default --detect sudo -Hiu $USER env conan profile update settings.compiler.libcxx=libstdc++11 default sudo -Hiu $USER env conan profile update conf.tools.system.package_manager:mode=install default sudo -Hiu $USER env conan profile update conf.tools.system.package_manager:sudo=True default + +export CPM_SOURCE_CACHE=$HOME/.cache/CPM \ No newline at end of file From 1fbaa90fa97969669245703bbb2e603dde69e4b7 Mon Sep 17 00:00:00 2001 From: psy_inf Date: Mon, 29 Apr 2024 21:19:45 +0200 Subject: [PATCH 6/6] testing serialization --- apps/serialization/customLoading.hpp | 178 +++++++++++++++++ apps/serialization/main._cpp | 267 -------------------------- apps/serialization/main2.cpp | 51 +---- apps/serialization/simpleLoadSave.hpp | 48 +++++ doc/todo.md | 9 + libs/pgEngine/math/Matrix.hpp | 151 +++++++++++++++ tests/Matrix/MatrixTests.cpp | 4 + 7 files changed, 393 insertions(+), 315 deletions(-) create mode 100644 apps/serialization/customLoading.hpp delete mode 100644 apps/serialization/main._cpp create mode 100644 apps/serialization/simpleLoadSave.hpp create mode 100644 doc/todo.md create mode 100644 libs/pgEngine/math/Matrix.hpp create mode 100644 tests/Matrix/MatrixTests.cpp diff --git a/apps/serialization/customLoading.hpp b/apps/serialization/customLoading.hpp new file mode 100644 index 00000000..74d08b6d --- /dev/null +++ b/apps/serialization/customLoading.hpp @@ -0,0 +1,178 @@ +#include +#include + +#include +#include +#include +#include +#include + +struct IdMetaAny +{ + entt::id_type id; + entt::meta_any any; + + template + void save(Ar& ar) const + { + ar(id); + any.invoke(entt::hashed_string("save"), entt::forward_as_meta(ar)); + } + + template + void load(Ar& ar) + { + ar(id); + any = entt::resolve(id).construct(); + any.invoke(entt::hashed_string("load"), entt::forward_as_meta(ar)); + } +}; + +template +void move_emplace(Component& elem, entt::sparse_set& storage, entt::entity entity) +{ + static_cast&>(storage).emplace(entity, std::move(elem)); +} + +template +void RegisterComponentForSerialize() +{ + using namespace entt::literals; + auto&& f = entt::meta().template func<&move_emplace>("emplace"_hs); + f = f.type(entt::type_id().hash()); + f = f.template func<&Component::template save>(entt::hashed_string("save")); + f = f.template func<&Component::template load>(entt::hashed_string("load")); +} + +struct EntityWrapper +{ + entt::registry& registry; + entt::entity handle; +}; + +template +void save(Ar& ar, const EntityWrapper& wrapper) +{ + std::map components; + + for (auto&& [id, storage] : wrapper.registry.storage()) + { + if (!storage.contains(wrapper.handle)) { continue; } + + if (auto type = entt::resolve(id); type) + { + components.emplace(id, IdMetaAny{id, type.from_void(storage.value(wrapper.handle))}); + } + } + ar(components); +} + +template +void load(Ar& ar, EntityWrapper& wrapper) +{ + using namespace entt::literals; + + std::map components; + ar(components); + + for (auto&& [id, storage] : wrapper.registry.storage()) + { + if (auto itr = components.find(id); itr == components.end()) + { + // undo for add component. + storage.remove(wrapper.handle); + } + else if (storage.contains(wrapper.handle)) + { + // undo for update component + auto& any = itr->second.any; + storage.remove(wrapper.handle); + any.type().invoke("emplace"_hs, any, entt::forward_as_meta(storage), wrapper.handle); + } + } + + for (const auto& [id, id_any] : components) + { + if (auto storage = wrapper.registry.storage(id); !storage->contains(wrapper.handle)) + { + // undo for remove component + id_any.any.type().invoke("emplace"_hs, id_any.any, entt::forward_as_meta(storage), wrapper.handle); + } + } +} + +struct Position +{ + double x, y, z; + + template + void save(Ar& ar) const + { + ar(x, y, z); + } + + template + void load(Ar& ar) + { + ar(x, y, z); + } +}; + +void no_entity_wrapper_case() +{ + entt::registry registry; + auto entity = registry.create(); + // initialize + registry.emplace(entity, 1.0, 2.0, 3.0); + + // back up + std::stringstream ss; + { + cereal::BinaryOutputArchive ar(ss); + ar(EntityWrapper{registry, entity}); + } + // update + { + auto& comp = registry.get(entity); + comp.x = 10; + comp.y = 20; + comp.z = 30; + } + // undo + { + cereal::BinaryInputArchive ar(ss); + EntityWrapper wrapper{registry, entity}; + ar(wrapper); + } + // check + { + auto& comp = registry.get(entity); + assert((comp.x == 1.0) && (comp.y == 2.0) && (comp.z == 3.0)); + } +} + +void testCustomLoading() +{ + RegisterComponentForSerialize(); + + no_entity_wrapper_case(); + + entt::registry registry; + for (auto i = 0; i < 10; ++i) + { + auto entity = registry.create(); + registry.emplace(entity, i * 0.1f, i * 0.01f, i * 1.0f); + } + // save all + // todo: for initial loading we need to store the list of entities as well + std::stringstream ss; + { + cereal::BinaryOutputArchive ar(ss); + auto view = registry.view(); + + for (const auto entity : view) + { + ar(EntityWrapper{registry, entity}); + } + } +} \ No newline at end of file diff --git a/apps/serialization/main._cpp b/apps/serialization/main._cpp deleted file mode 100644 index a3d2baea..00000000 --- a/apps/serialization/main._cpp +++ /dev/null @@ -1,267 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -struct IdMetaAny -{ - entt::id_type id; - entt::meta_any any; - - template - void save(Ar& ar) const - { - ar(id); - any.invoke(entt::hashed_string("save"), entt::forward_as_meta(ar)); - } - - template - void load(Ar& ar) - { - ar(id); - any = entt::resolve(id).construct(); - any.invoke(entt::hashed_string("load"), entt::forward_as_meta(ar)); - } -}; - -template -void move_emplace(Component& elem, entt::sparse_set& storage, entt::entity entity) -{ - static_cast&>(storage).emplace(entity, std::move(elem)); -} - -template -void RegisterComponentForSerialize() -{ - using namespace entt::literals; - auto&& f = entt::meta().template func<&move_emplace>("emplace"_hs); - f = f.type(entt::type_id().hash()); - f = f.template func<&Component::template save>(entt::hashed_string("save")); - f = f.template func<&Component::template load>(entt::hashed_string("load")); -} - -struct Entity -{ - Entity(entt::registry* in_registry) - : registry{in_registry} - { - if (this->registry) { this->handle = this->registry->create(); } - } - - template - Comp& add(Args&&... args) - { - return this->registry->emplace(this->handle, args...); - } - - template - Comp* get() - { - return this->registry->try_get(this->handle); - } - - template - void remove() - { - this->registry->remove(this->handle); - } - - template - void save(Ar& ar) const - { - std::map components; - - for (auto&& [id, storage] : this->registry->storage()) - { - if (!storage.contains(this->handle)) { continue; } - - if (auto type = entt::resolve(id); type) - { - components.emplace(id, IdMetaAny{id, type.from_void(storage.value(this->handle))}); - } - } - ar(components); - } - - template - void load(Ar& ar) - { - using namespace entt::literals; - - std::map components; - ar(components); - - for (auto&& [id, storage] : this->registry->storage()) - { - if (auto itr = components.find(id); itr == components.end()) - { - // undo for add component. - storage.remove(this->handle); - } - else if (storage.contains(this->handle)) - { - // undo for update component - auto& any = itr->second.any; - storage.remove(this->handle); - any.type().invoke("emplace"_hs, any, entt::forward_as_meta(storage), this->handle); - } - } - - for (const auto& [id, id_any] : components) - { - if (auto storage = this->registry->storage(id); !storage->contains(this->handle)) - { - // undo for remove component - id_any.any.type().invoke("emplace"_hs, id_any.any, entt::forward_as_meta(storage), this->handle); - } - } - } - -private: - entt::registry* registry = nullptr; - entt::entity handle{entt::null}; -}; - -struct Position -{ - double x, y, z; - - template - void save(Ar& ar) const - { - ar(x, y, z); - } - - template - void load(Ar& ar) - { - ar(x, y, z); - } -}; - -bool trivially_copyable_case() -{ - entt::registry registry; - Entity e{®istry}; - - // initialize - e.add(1.0, 2.0, 3.0); - - // back up - std::stringstream ss; - { - cereal::BinaryOutputArchive ar(ss); - ar(e); - } - // update - { - auto comp = e.get(); - comp->x = 10; - comp->y = 20; - comp->z = 30; - } - // undo - { - cereal::BinaryInputArchive ar(ss); - ar(e); - } - // check - { - auto comp = e.get(); - return (comp != nullptr) && (comp->x == 1.0) && (comp->y == 2.0) && (comp->z == 3.0); - } -} - -struct Data -{ - std::unique_ptr value; - - template - void save(Ar& ar) const - { - ar(value); - } - - template - void load(Ar& ar) - { - ar(value); - } -}; - -bool unique_ptr_case() -{ - entt::registry registry; - Entity e{®istry}; - - // initialize - auto& comp = e.add(); - comp.value = std::make_unique(10.0); - - // back up - std::stringstream ss; - { - cereal::BinaryOutputArchive ar(ss); - ar(e); - } - // update - { - auto comp = e.get(); - comp->value = nullptr; - } - // undo - { - cereal::BinaryInputArchive ar(ss); - ar(e); - } - // check - { - auto comp = e.get(); - return (comp != nullptr) && (comp->value != nullptr) && (*(comp->value) == 10.0); - } -} - -bool simple() -{ - entt::registry registry; - auto entity = registry.create(); - - // initialize - auto d = Data{std::make_unique(10.0)}; - registry.emplace(entity, std::move(d)); - - // back up - std::stringstream ss; - { - cereal::BinaryOutputArchive ar(ss); - ar(entity); - } - // update - { - auto comp = registry.try_get(entity); - comp->value = nullptr; - } - // undo - { - cereal::BinaryInputArchive ar(ss); - ar(entity); - } - // check - { - auto comp = registry.try_get(entity); - return (comp != nullptr) && (comp->value != nullptr) && (*(comp->value) == 10.0); - } -} - -int main() -{ - RegisterComponentForSerialize(); - RegisterComponentForSerialize(); - simple(); - assert(trivially_copyable_case() && L"fail to trivially_copyable_case"); - assert(unique_ptr_case() && L"fail to unique_ptr_case"); -} \ No newline at end of file diff --git a/apps/serialization/main2.cpp b/apps/serialization/main2.cpp index 22a406f0..d9981b37 100644 --- a/apps/serialization/main2.cpp +++ b/apps/serialization/main2.cpp @@ -1,54 +1,9 @@ #include #include - -#include -#include -#include -#include -#include - -struct Position -{ - int x; - int y; -}; - -template -void serialize(Archive& archive, Position& position) -{ - archive(position.x, position.y); -} - -void saveRegistry(entt::registry& reg, std::string_view file_name) -{ - std::ofstream storage("../data/test.out"); - - cereal::JSONOutputArchive output{storage}; - - entt::snapshot{reg}.get(output).get(output); -} - -void loadRegistry(entt::registry& reg, std::string_view file_name) -{ - std::ifstream storage("../data/test.out"); - - cereal::JSONInputArchive output{storage}; - - entt::basic_snapshot_loader{reg}.get(output).get(output); -} +#include +#include "customLoading.hpp" int main() { - entt::registry registry; - auto entity = registry.create(); - registry.emplace(entity, Position{42, 42}); - // registry.add(registry.create(), {3, 14}); - saveRegistry(registry, "data/test.out"); - { - auto& pos = registry.get(entity); - pos.x = 0; - } - registry.clear(); - loadRegistry(registry, "data/test.out"); - auto& pos2 = registry.get(entity); + testCustomLoading(); } \ No newline at end of file diff --git a/apps/serialization/simpleLoadSave.hpp b/apps/serialization/simpleLoadSave.hpp new file mode 100644 index 00000000..8c62c5b0 --- /dev/null +++ b/apps/serialization/simpleLoadSave.hpp @@ -0,0 +1,48 @@ +#include +#include + +struct Position +{ + int x; + int y; +}; + +template +void serialize(Archive& archive, Position& position) +{ + archive(position.x, position.y); +} + +void saveRegistry(entt::registry& reg, std::string_view file_name) +{ + std::ofstream storage("../data/test.out"); + + cereal::JSONOutputArchive output{storage}; + + entt::snapshot{reg}.get(output).get(output); +} + +void loadRegistry(entt::registry& reg, std::string_view file_name) +{ + std::ifstream storage("../data/test.out"); + + cereal::JSONInputArchive output{storage}; + + entt::basic_snapshot_loader{reg}.get(output).get(output); +} + +void testSimpleLoading() +{ // + entt::registry registry; + auto entity = registry.create(); + registry.emplace(entity, Position{42, 42}); + // registry.add(registry.create(), {3, 14}); + saveRegistry(registry, "data/test.out"); + { + auto& pos = registry.get(entity); + pos.x = 0; + } + registry.clear(); + loadRegistry(registry, "data/test.out"); + auto& pos2 = registry.get(entity); +} \ No newline at end of file diff --git a/doc/todo.md b/doc/todo.md new file mode 100644 index 00000000..34f38acc --- /dev/null +++ b/doc/todo.md @@ -0,0 +1,9 @@ +* use a virtual 'filesystem' https://github.com/icculus/physfs +* experiment using serialization to save an load inital scenes and game state +** check https://github.com/skypjack/entt/issues/10 00 for iterating over all components +* use a scripting language to define game logic and behavior +* use a scripting language to define game assets +* use json/yaml config files +* use homogenous matrices for all transformations +* https://github.com/matepek/catch2-with-gmock?tab=readme-ov-file mocking +* replace cache with entt types (some inspiration here: https://github.com/trollworks/sdk-core) diff --git a/libs/pgEngine/math/Matrix.hpp b/libs/pgEngine/math/Matrix.hpp new file mode 100644 index 00000000..f78f4dc7 --- /dev/null +++ b/libs/pgEngine/math/Matrix.hpp @@ -0,0 +1,151 @@ +#pragma once +#include + +namespace pg { +template +struct Matrix +{ + static constexpr uint8_t rows = 3; + static constexpr uint8_t cols = 3; + std::array data = {0, 0, 0, 0, 1, 0, 0, 0, 1}; + + T& operator()(uint8_t row, uint8_t col) { return data[row * cols + col]; } + + const T& operator()(uint8_t row, uint8_t col) const { return data[row * cols + col]; } + + // filled with zeros + static Matrix makeZero() + { + Matrix result; + result.data.fill(0); + return result; + } + + // unity matrix + static Matrix makeIdentity() + { + Matrix result; + return result; + } + + // from translation vector + static Matrix makeTrans(const Vec2& v) + { + Matrix result; + result(0, 2) = v[0]; + result(1, 2) = v[1]; + return result; + } + + // from scale vector + static Matrix makeScale(const Vec2& v) + { + Matrix result; + result(0, 0) = v[0]; + result(1, 1) = v[1]; + return result; + } + + // from rotation angle + static Matrix makeRot(T angle) + { + Matrix result; + result(0, 0) = std::cos(angle); + result(0, 1) = -std::sin(angle); + result(1, 0) = std::sin(angle); + result(1, 1) = std::cos(angle); + return result; + } + + // projection matrix + static Matrix makeProjection(T left, T right, T bottom, T top, T near, T far) + { + Matrix result; + result(0, 0) = 2 / (right - left); + result(1, 1) = 2 / (top - bottom); + result(2, 2) = -2 / (far - near); + result(0, 3) = -(right + left) / (right - left); + result(1, 3) = -(top + bottom) / (top - bottom); + result(2, 3) = -(far + near) / (far - near); + result(3, 3) = 1; + return result; + } + + // matrix multiplication + Matrix operator*(const Matrix& other) const + { + Matrix result; + for (auto i = 0u; i < rows; ++i) + { + for (auto j = 0u; j < cols; ++j) + { + for (uint8_t k = 0; k < cols; ++k) + { + result(i, j) += (*this)(i, k) * other(k, j); + } + } + } + return result; + } + + // matrix vector multiplication + Vec2 operator*(const Vec2& v) const + { + Vec2 result; + for (auto i = 0u; i < rows; ++i) + { + for (auto j = 0u; j < cols; ++j) + { + result[i] += (*this)(i, j) * v[j]; + } + } + return result; + } + + // transpose matrix + + Matrix transpose() const + { + Matrix result; + for (auto i = 0u; i < rows; ++i) + { + for (auto j = 0u; j < cols; ++j) + { + result(i, j) = (*this)(j, i); + } + } + return result; + } + + // matrix determinant + T determinant() const + { + return (*this)(0, 0) * (*this)(1, 1) * (*this)(2, 2) + (*this)(0, 1) * (*this)(1, 2) * (*this)(2, 0) + + (*this)(0, 2) * (*this)(1, 0) * (*this)(2, 1) - (*this)(0, 2) * (*this)(1, 1) * (*this)(2, 0) - + (*this)(0, 1) * (*this)(1, 0) * (*this)(2, 2) - (*this)(0, 0) * (*this)(1, 2) * (*this)(2, 1); + } + + // matrix inverse + Matrix inverse() const + { + Matrix result; + T det = determinant(); + if (det == 0) { return result; } + result(0, 0) = ((*this)(1, 1) * (*this)(2, 2) - (*this)(1, 2) * (*this)(2, 1)) / det; + result(0, 1) = ((*this)(0, 2) * (*this)(2, 1) - (*this)(0, 1) * (*this)(2, 2)) / det; + result(0, 2) = ((*this)(0, 1) * (*this)(1, 2) - (*this)(0, 2) * (*this)(1, 1)) / det; + result(1, 0) = ((*this)(1, 2) * (*this)(2, 0) - (*this)(1, 0) * (*this)(2, 2)) / det; + result(1, 1) = ((*this)(0, 0) * (*this)(2, 2) - (*this)(0, 2) * (*this)(2, 0)) / det; + result(1, 2) = ((*this)(0, 2) * (*this)(1, 0) - (*this)(0, 0) * (*this)(1, 2)) / det; + result(2, 0) = ((*this)(1, 0) * (*this)(2, 1) - (*this)(1, 1) * (*this)(2, 0)) / det; + result(2, 1) = ((*this)(0, 1) * (*this)(2, 0) - (*this)(0, 0) * (*this)(2, 1)) / det; + result(2, 2) = ((*this)(0, 0) * (*this)(1, 1) - (*this)(0, 1) * (*this)(1, 0)) / det; + return result; + } + + void rotate(T angle) { *this = rotation(angle) * *this; } + + void scale(const Vec2& v) { *this = scale(v) * *this; } +}; + +} // namespace pg \ No newline at end of file diff --git a/tests/Matrix/MatrixTests.cpp b/tests/Matrix/MatrixTests.cpp new file mode 100644 index 00000000..fb18e112 --- /dev/null +++ b/tests/Matrix/MatrixTests.cpp @@ -0,0 +1,4 @@ +#include +#include +#include +#include