Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 35 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ add_subdirectory(asteroids)
add_subdirectory(textdemo)
add_subdirectory(stellarfield)
add_subdirectory(galaxy)
add_subdirectory(serialization)
29 changes: 29 additions & 0 deletions apps/serialization/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
)

enable_coverage(${PROJECT_NAME})

install(TARGETS ${PROJECT_NAME})
178 changes: 178 additions & 0 deletions apps/serialization/customLoading.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#include <sstream>
#include <string>

#include <cereal/archives/binary.hpp>
#include <cereal/cereal.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/memory.hpp>
#include <entt/entt.hpp>

struct IdMetaAny
{
entt::id_type id;
entt::meta_any any;

template <class Ar>
void save(Ar& ar) const
{
ar(id);
any.invoke(entt::hashed_string("save"), entt::forward_as_meta(ar));
}

template <class Ar>
void load(Ar& ar)
{
ar(id);
any = entt::resolve(id).construct();
any.invoke(entt::hashed_string("load"), entt::forward_as_meta(ar));
}
};

template <typename Component>
void move_emplace(Component& elem, entt::sparse_set& storage, entt::entity entity)
{
static_cast<entt::storage_for_t<Component>&>(storage).emplace(entity, std::move(elem));
}

template <class Component>
void RegisterComponentForSerialize()
{
using namespace entt::literals;
auto&& f = entt::meta<Component>().template func<&move_emplace<Component>>("emplace"_hs);
f = f.type(entt::type_id<Component>().hash());
f = f.template func<&Component::template save<cereal::BinaryOutputArchive>>(entt::hashed_string("save"));
f = f.template func<&Component::template load<cereal::BinaryInputArchive>>(entt::hashed_string("load"));
}

struct EntityWrapper
{
entt::registry& registry;
entt::entity handle;
};

template <class Ar>
void save(Ar& ar, const EntityWrapper& wrapper)
{
std::map<entt::id_type, IdMetaAny> 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 <class Ar>
void load(Ar& ar, EntityWrapper& wrapper)
{
using namespace entt::literals;

std::map<entt::id_type, IdMetaAny> 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 <class Ar>
void save(Ar& ar) const
{
ar(x, y, z);
}

template <class Ar>
void load(Ar& ar)
{
ar(x, y, z);
}
};

void no_entity_wrapper_case()
{
entt::registry registry;
auto entity = registry.create();
// initialize
registry.emplace<Position>(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<Position>(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<Position>(entity);
assert((comp.x == 1.0) && (comp.y == 2.0) && (comp.z == 3.0));
}
}

void testCustomLoading()
{
RegisterComponentForSerialize<Position>();

no_entity_wrapper_case();

entt::registry registry;
for (auto i = 0; i < 10; ++i)
{
auto entity = registry.create();
registry.emplace<Position>(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<entt::entity>();

for (const auto entity : view)
{
ar(EntityWrapper{registry, entity});
}
}
}
9 changes: 9 additions & 0 deletions apps/serialization/main2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <fstream>
#include <string>
#include <map>
#include "customLoading.hpp"

int main()
{
testCustomLoading();
}
48 changes: 48 additions & 0 deletions apps/serialization/simpleLoadSave.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <cereal/archives/json.hpp>
#include <entt/entt.hpp>

struct Position
{
int x;
int y;
};

template <typename Archive>
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<entt::entity>(output).get<Position>(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<entt::entity>(output).get<Position>(output);
}

void testSimpleLoading()
{ //
entt::registry registry;
auto entity = registry.create();
registry.emplace<Position>(entity, Position{42, 42});
// registry.add<Position>(registry.create(), {3, 14});
saveRegistry(registry, "data/test.out");
{
auto& pos = registry.get<Position>(entity);
pos.x = 0;
}
registry.clear();
loadRegistry(registry, "data/test.out");
auto& pos2 = registry.get<Position>(entity);
}
2 changes: 2 additions & 0 deletions doc/setup_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions doc/todo.md
Original file line number Diff line number Diff line change
@@ -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)
Loading