├── docs ├── img │ ├── logo.png │ ├── tracy_1.png │ ├── tracy_2.png │ └── logo_small.png └── CMakeLists.txt ├── src ├── examples │ ├── example2 │ │ ├── src │ │ │ ├── dummy.cpp │ │ │ └── main.cpp │ │ └── CMakeLists.txt │ ├── example_dll │ │ ├── src │ │ │ └── dll_api.cpp │ │ ├── CMakeLists.txt │ │ └── include │ │ │ └── dll_api.h │ ├── example_app │ │ ├── src │ │ │ └── main.cpp │ │ └── CMakeLists.txt │ ├── example_external │ │ ├── Makefile │ │ ├── src │ │ │ └── main.cpp │ │ └── CMakeLists.txt │ ├── example3 │ │ ├── CMakeLists.txt │ │ └── src │ │ │ └── main.cpp │ ├── example1 │ │ ├── CMakeLists.txt │ │ └── src │ │ │ └── main.cpp │ ├── example_roguelike │ │ └── CMakeLists.txt │ └── example_wasm │ │ ├── src │ │ ├── gaia_example_wasm.html │ │ └── main.cpp │ │ └── CMakeLists.txt ├── perf │ ├── app │ │ └── CMakeLists.txt │ ├── mt │ │ └── CMakeLists.txt │ ├── duel │ │ └── CMakeLists.txt │ ├── iter │ │ └── CMakeLists.txt │ ├── entity │ │ └── CMakeLists.txt │ └── CMakeLists.txt └── test │ └── CMakeLists.txt ├── .gitattributes ├── .vscode ├── extensions.json ├── settings.json ├── conan-settings.json ├── tasks.json └── launch.json ├── vm ├── build.sh ├── readme.md ├── setup.sh ├── setup_podman.sh ├── setup.bat ├── Dockerfile ├── amd64.Dockerfile ├── setup_podman.bat └── build_clang_cachegrind.sh ├── pkg ├── conan │ ├── test_package │ │ ├── test_package.cpp │ │ ├── CMakeLists.txt │ │ └── conanfile.py │ ├── conandata.yml │ └── conanfile.py └── cmake │ └── gaiaConfig.cmake.in ├── .editorconfig ├── .gitignore ├── include ├── gaia │ ├── config │ │ ├── version.h │ │ └── config.h │ ├── cnt │ │ ├── darray.h │ │ ├── darray_soa.h │ │ ├── set.h │ │ ├── sarray.h │ │ ├── map.h │ │ ├── darray_ext.h │ │ ├── sarray_ext.h │ │ ├── sarray_soa.h │ │ ├── darray_ext_soa.h │ │ ├── sarray_ext_soa.h │ │ ├── fwd_llist.h │ │ └── bitset_iterator.h │ ├── ecs │ │ ├── id_fwd.h │ │ ├── query_fwd.h │ │ ├── component_getter.h │ │ ├── command_buffer_fwd.h │ │ ├── common.h │ │ ├── ser_binary.h │ │ ├── archetype_common.h │ │ ├── api.h │ │ ├── component_setter.h │ │ ├── query_mask.h │ │ ├── api.inl │ │ ├── chunk_header.h │ │ ├── archetype_graph.h │ │ └── component.h │ ├── core │ │ ├── span.h │ │ ├── string.h │ │ ├── dyn_singleton.h │ │ ├── func.h │ │ ├── bit_utils.h │ │ ├── hashing_string.h │ │ ├── iterator.h │ │ └── hashing_policy.h │ ├── mt │ │ ├── spinlock.h │ │ ├── semaphore_fast.h │ │ ├── jobcommon.h │ │ ├── semaphore.h │ │ ├── event.h │ │ ├── jobhandle.h │ │ └── futex.h │ ├── mem │ │ ├── raw_data_holder.h │ │ ├── mem_sani.h │ │ └── stack_allocator.h │ ├── meta │ │ └── type_info.h │ └── ser │ │ ├── ser_rt.h │ │ ├── ser_buffer_binary.h │ │ ├── ser_ct.h │ │ └── ser_common.h └── gaia.h ├── make_single_header.bat ├── make_single_header.sh ├── Makefile ├── .clangd ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── docs.yml │ ├── sanitize.yml │ └── coverage.yml ├── .clang-tidy ├── LICENSE ├── .clang-format ├── CONTRIBUTING.md ├── gaia.code-workspace └── CMakeLists.txt /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/HEAD/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/tracy_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/HEAD/docs/img/tracy_1.png -------------------------------------------------------------------------------- /docs/img/tracy_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/HEAD/docs/img/tracy_2.png -------------------------------------------------------------------------------- /docs/img/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/HEAD/docs/img/logo_small.png -------------------------------------------------------------------------------- /src/examples/example2/src/dummy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Example source file also including gaia.h -------------------------------------------------------------------------------- /src/perf/app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_app") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/perf/mt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_mt") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/perf/duel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_duel") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/perf/iter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_iter") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/perf/entity/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_entity") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/examples/example_dll/src/dll_api.cpp: -------------------------------------------------------------------------------- 1 | #include "dll_api.h" 2 | 3 | void WorldTest::cleanup() { 4 | m_world.cleanup(); 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h eol=lf 2 | *.hpp eol=lf 3 | *.cpp eol=lf 4 | *.inl eol=lf 5 | *.md eol=lf 6 | *.sh eol=lf 7 | *.txt eol=lf 8 | *.bat eol=crlf -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "kr4is.cpptools-extension-pack", 4 | "ms-azuretools.vscode-docker" 5 | ] 6 | } -------------------------------------------------------------------------------- /vm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! bash ./build_clang.sh "${@:1}"; then 4 | exit 1 5 | fi 6 | 7 | if ! bash ./build_gcc.sh "${@:1}"; then 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /pkg/conan/test_package/test_package.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | gaia::ecs::World world; 5 | gaia::ecs::Entity e = world.add(); 6 | (void)e; 7 | 8 | return 0; 9 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | *.h end_of_line=lf 2 | *.hpp end_of_line=lf 3 | *.cpp end_of_line=lf 4 | *.inl end_of_line=lf 5 | *.md end_of_line=lf 6 | *.sh end_of_line=lf 7 | *.txt end_of_line=lf 8 | *.bat end_of_line=crlf -------------------------------------------------------------------------------- /pkg/conan/conandata.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | "0.9.2": 3 | url: "https://github.com/richardbiely/gaia-ecs/archive/refs/tags/v0.9.2.tar.gz" 4 | sha256: "2fbed6c70fb80ae20812adfe073a0d2b396c447b56167be2c917979fdc95cc8d" -------------------------------------------------------------------------------- /src/examples/example_app/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "dll_api.h" 2 | 3 | using namespace gaia; 4 | 5 | int main() { 6 | // Create a dll world 7 | WorldTest dll_world; 8 | dll_world.cleanup(); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false, 3 | "editor.formatOnSave": true, 4 | "C_Cpp.default.cStandard": "c17", 5 | "C_Cpp.default.cppStandard": "c++17", 6 | "makefile.configureOnOpen": false 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *build* 2 | !vm/**/build_*.sh 3 | !vm/**/build.sh 4 | *.bin 5 | *.db 6 | ninja 7 | out 8 | cachegrind.* 9 | .vs 10 | .cache 11 | CMakeCache.txt 12 | CMakeSettings.json 13 | CMakeFiles 14 | CMakeUserPresets.json 15 | .DS_Store -------------------------------------------------------------------------------- /include/gaia/config/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Breaking changes and big features 4 | #define GAIA_VERSION_MAJOR 0 5 | // Smaller changes and features 6 | #define GAIA_VERSION_MINOR 9 7 | // Fixes and tweaks 8 | #define GAIA_VERSION_PATCH 3 9 | -------------------------------------------------------------------------------- /include/gaia/cnt/darray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/darray_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using darray = cnt::darr; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /make_single_header.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | 3 | set "PATH_TO_AMALGAMATE_DIR=%1" 4 | set "PATH_TO_AMALGAMATE=%PATH_TO_AMALGAMATE_DIR%/amalgamate" 5 | del single_include/gaia.h 6 | "%PATH_TO_AMALGAMATE%" -i include -w "*.cpp;*.h;*.hpp;*.inl" include/gaia.h single_include/gaia.h -------------------------------------------------------------------------------- /include/gaia/cnt/darray_soa.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/darray_soa_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using darray_soa = cnt::darr_soa; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/external/robin_hood.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using set = robin_hood::unordered_flat_set; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /make_single_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH_TO_AMALGAMATE_DIR="${@:1}" 4 | PATH_TO_AMALGAMATE=${PATH_TO_AMALGAMATE_DIR}/amalgamate 5 | rm -f ./single_include/gaia.h 6 | ${PATH_TO_AMALGAMATE} -i ./include -w "*.cpp;*.h;*.hpp;*.inl" ./include/gaia.h ./single_include/gaia.h -------------------------------------------------------------------------------- /pkg/cmake/gaiaConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set(GAIA_VERSION "@PROJECT_VERSION@") 4 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") 5 | 6 | set_and_check(gaia_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") 7 | check_required_components("@PROJECT_NAME@") -------------------------------------------------------------------------------- /include/gaia/cnt/sarray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/sarray_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using sarray = cnt::sarr; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/external/robin_hood.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using map = robin_hood::unordered_flat_map; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/darray_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/darray_ext_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using darray_ext = cnt::darr_ext; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/sarray_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/sarray_ext_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using sarray_ext = cnt::sarr_ext; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/sarray_soa.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/sarray_soa_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using sarray_soa = cnt::sarr_soa; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /pkg/conan/test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(test_package CXX) 3 | 4 | find_package(gaia REQUIRED CONFIG) 5 | add_executable(test_package test_package.cpp) 6 | target_link_libraries(test_package gaia::gaia) 7 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) -------------------------------------------------------------------------------- /src/examples/example_external/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "run - clean, build, and run" 3 | @echo "clean - remove built files" 4 | 5 | run: clean build 6 | 7 | build: 8 | cd ../.. && $(MAKE) install 9 | mkdir build && cd build && cmake .. && $(MAKE) run 10 | 11 | clean: 12 | rm -rf ./build || true -------------------------------------------------------------------------------- /include/gaia/cnt/darray_ext_soa.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/darray_ext_soa_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using darray_ext_soa = cnt::darr_ext_soa; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/cnt/sarray_ext_soa.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/cnt/impl/sarray_ext_soa_impl.h" 4 | 5 | namespace gaia { 6 | namespace cnt { 7 | template 8 | using sarray_ext_soa = cnt::sarr_ext_soa; 9 | } // namespace cnt 10 | } // namespace gaia 11 | -------------------------------------------------------------------------------- /include/gaia/ecs/id_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace gaia { 5 | namespace ecs { 6 | using IdentifierId = uint32_t; 7 | using IdentifierData = uint32_t; 8 | 9 | using EntityId = IdentifierId; 10 | using ComponentId = IdentifierId; 11 | } // namespace ecs 12 | } // namespace gaia -------------------------------------------------------------------------------- /src/examples/example3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example3") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | 4 | target_include_directories(${PROJ_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads REQUIRED) 8 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) -------------------------------------------------------------------------------- /src/examples/example1/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example1") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | 4 | target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads REQUIRED) 8 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) -------------------------------------------------------------------------------- /src/examples/example_app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example_app") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | 4 | target_include_directories(${PROJ_NAME} 5 | PRIVATE ${PROJECT_SOURCE_DIR}/include 6 | PUBLIC ${PROJECT_CURRENT_SOURCE_DIR}/../../example_dll/include) 7 | 8 | target_link_libraries(${PROJ_NAME} PRIVATE gaia_example_dll) 9 | -------------------------------------------------------------------------------- /src/examples/example2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example2") 2 | add_executable(${PROJ_NAME} src/main.cpp src/dummy.cpp) 3 | 4 | target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads REQUIRED) 8 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) -------------------------------------------------------------------------------- /src/examples/example_roguelike/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example_roguelike") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | 4 | target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads REQUIRED) 8 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) -------------------------------------------------------------------------------- /src/examples/example_wasm/src/gaia_example_wasm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Gaia-ECS WASM 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "test - clean, build, and test" 3 | @echo "clean - remove built files" 4 | @echo "install - install" 5 | 6 | install: clean 7 | mkdir build && cd build && cmake .. && cmake --build . --config Release --target install 8 | 9 | test: clean build 10 | 11 | build: 12 | mkdir build && cd build && cmake .. && $(MAKE) test 13 | 14 | clean: 15 | rm -rf ./build || true -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-std=c++17, -Wall, -Wextra, -Wextra-semi, -Wextra-tokens, -pedantic, -pedantic-errors, -Wpadded, -Wpadded-bitfield, -Wshadow, -Wcast-align, -Wunused, -Wconversion, -Wsign-conversion, -Wnull-dereference, -Wdouble-promotion, -Wformat=2, -Wimplicit-fallthrough, -Wduplicated-cond, -Wduplicated-branches, -Wlogical-op, -Wuseless-cast, -Wunused-parameter, -Wunused-local-typedef] -------------------------------------------------------------------------------- /src/examples/example1/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace gaia; 4 | 5 | struct Foo {}; 6 | 7 | void print_info(ecs::Entity e) { 8 | GAIA_LOG_N("entity %u:%u", e.id(), e.gen()); 9 | } 10 | 11 | int main() { 12 | ecs::World w; 13 | auto e = w.add(); 14 | auto e2 = w.add(); 15 | w.add(e2); 16 | print_info(e); 17 | 18 | auto q = w.query().all(); 19 | q.each([](ecs::Entity ent) { 20 | print_info(ent); 21 | }); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/example3/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace gaia; 4 | 5 | struct Foo {}; 6 | 7 | void print_info(ecs::Entity e) { 8 | GAIA_LOG_N("entity %u:%u", e.id(), e.gen()); 9 | } 10 | 11 | int main() { 12 | ecs::World w; 13 | auto e = w.add(); 14 | auto e2 = w.add(); 15 | w.add(e2); 16 | print_info(e); 17 | 18 | auto q = w.query().all(); 19 | q.each([](ecs::Entity ent) { 20 | print_info(ent); 21 | }); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/example_external/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace gaia; 4 | 5 | struct Foo {}; 6 | 7 | void print_info(ecs::Entity e) { 8 | GAIA_LOG_N("entity %u:%u", e.id(), e.gen()); 9 | } 10 | 11 | int main() { 12 | ecs::World w; 13 | auto e = w.add(); 14 | auto e2 = w.add(); 15 | w.add(e2); 16 | print_info(e); 17 | 18 | auto q = w.query().all(); 19 | q.each([](ecs::Entity ent) { 20 | print_info(ent); 21 | }); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/example_dll/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example_dll") 2 | 3 | add_library(${PROJ_NAME} SHARED 4 | src/dll_api.cpp 5 | ) 6 | 7 | if(GAIA_PROFILER_CPU OR GAIA_PROFILER_MEM) 8 | set_property(TARGET TracyClient PROPERTY POSITION_INDEPENDENT_CODE ON) 9 | endif() 10 | 11 | target_include_directories(${PROJ_NAME} 12 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include 13 | PRIVATE ${PROJECT_SOURCE_DIR}/include 14 | ) 15 | 16 | target_compile_definitions(${PROJ_NAME} PRIVATE BUILDING_DLL) -------------------------------------------------------------------------------- /src/perf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Preprocessor 2 | add_compile_definitions(GAIA_DEBUG=0) 3 | 4 | include_directories(${PROJECT_SOURCE_DIR}/include) 5 | include_directories(${picobench_SOURCE_DIR}/include) 6 | 7 | set(THREADS_PREFER_PTHREAD_FLAG ON) 8 | find_package(Threads REQUIRED) 9 | link_libraries(Threads::Threads) 10 | link_libraries(picobench::picobench) 11 | 12 | add_subdirectory(iter) 13 | add_subdirectory(duel) 14 | add_subdirectory(entity) 15 | add_subdirectory(mt) 16 | add_subdirectory(app) 17 | -------------------------------------------------------------------------------- /include/gaia/core/span.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | // The same gaia headers used inside span_impl.h must be included here. 5 | // Amalgamated file would not be generated properly otherwise 6 | // because of the conditional nature of this file's usage. 7 | #include "gaia/core/iterator.h" 8 | #include "gaia/core/utility.h" 9 | 10 | #if GAIA_USE_STD_SPAN 11 | #include 12 | #else 13 | #include "gaia/core/impl/span_impl.h" 14 | namespace std { 15 | using gaia::core::span; 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /include/gaia/ecs/query_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace gaia { 5 | namespace ser { 6 | class ser_buffer_binary_dyn; 7 | } 8 | 9 | namespace ecs { 10 | class World; 11 | class Archetype; 12 | struct Entity; 13 | 14 | using QueryId = uint32_t; 15 | using GroupId = uint32_t; 16 | using QuerySerBuffer = ser::ser_buffer_binary_dyn; 17 | 18 | using TSortByFunc = int (*)(const World&, const void*, const void*); 19 | using TGroupByFunc = GroupId (*)(const World&, const Archetype&, Entity); 20 | } // namespace ecs 21 | } // namespace gaia -------------------------------------------------------------------------------- /src/examples/example_dll/include/dll_api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #ifdef BUILDING_DLL 5 | #define ENGINE_API GAIA_EXPORT 6 | #else 7 | #define ENGINE_API GAIA_IMPORT 8 | #endif 9 | 10 | class WorldTest { 11 | public: 12 | // Windows enforces explicit symbol import/export across DLL boundaries. Therefore, for the constructors 13 | // to be visible inside executables we need to also export the constuctor and destructor. Or mark the whole 14 | // class ENGINE_API. 15 | WorldTest() = default; 16 | virtual ~WorldTest() = default; 17 | 18 | ENGINE_API void cleanup(); 19 | 20 | private: 21 | gaia::ecs::World m_world; 22 | }; 23 | -------------------------------------------------------------------------------- /src/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_test") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | 4 | # System-specific threading library 5 | set(THREADS_PREFER_PTHREAD_FLAG ON) 6 | find_package(Threads REQUIRED) 7 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) 8 | 9 | # Unit test framework 10 | target_link_libraries(${PROJ_NAME} PRIVATE doctest::doctest) 11 | 12 | # Project files 13 | target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 14 | 15 | if(MSVC) 16 | enable_cxx_compiler_flag_if_supported("/bigobj") 17 | endif() 18 | 19 | include(CTest) 20 | enable_testing() 21 | add_test(NAME ${PROJ_NAME} COMMAND ${PROJ_NAME}) 22 | -------------------------------------------------------------------------------- /include/gaia/core/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include "gaia/core/span.h" 5 | 6 | namespace gaia { 7 | namespace core { 8 | inline bool is_whitespace(char c) { 9 | return c == ' ' || (c >= '\t' && c <= '\r'); 10 | } 11 | 12 | inline auto trim(std::span expr) { 13 | if (expr.empty()) 14 | return std::span{}; 15 | 16 | uint32_t beg = 0; 17 | while (is_whitespace(expr[beg])) 18 | ++beg; 19 | uint32_t end = (uint32_t)expr.size() - 1; 20 | while (end > beg && is_whitespace(expr[end])) 21 | --end; 22 | return expr.subspan(beg, end - beg + 1); 23 | } 24 | } // namespace core 25 | } // namespace gaia -------------------------------------------------------------------------------- /vm/readme.md: -------------------------------------------------------------------------------- 1 | # VM Container Setup and Build Instructions 2 | 3 | Prepare a docker container (inside the "vm" folder): 4 | 5 | * MacOS: bash ./setup.sh 6 | * Windows: bash ./setup.bat 7 | 8 | Prepare a podman container (inside the "vm" folder): 9 | 10 | * MacOS: bash ./setup_podman.sh 11 | * Windows: bash ./setup_podman.bat 12 | 13 | Once inside the virtual machine you can build the project: 14 | 15 | * Both Clang & GCC: ./build.sh 16 | * Clang only: ./build_clang.sh 17 | * GCC only: ./build_gcc.sh 18 | * Cachegrid: ./build_clang_cachegrind.sh 19 | 20 | Meant for internal usage primarily on Windows and MacOS. 21 | On Linux you likely already have both Clang or GCC set up. 22 | If you do not, it is also fine. -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [RichardBiely] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: paypal.me/richardbiely 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ## To Reproduce 15 | 16 | Steps to reproduce the behavior: 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ## Expected behavior 24 | 25 | A clear and concise description of what you expected to happen. 26 | 27 | ## Screenshots 28 | 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | ## Additional context 32 | 33 | Add any other context about the problem here such as hardware, operating system, compiler.... 34 | -------------------------------------------------------------------------------- /src/examples/example_external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Example of how to link an external project with gaia-ecs library 2 | # previously installed somewhere 3 | 4 | cmake_minimum_required(VERSION 3.12) 5 | 6 | set(PROJ_NAME "cli") 7 | project(${PROJ_NAME} LANGUAGES CXX) 8 | 9 | find_package(gaia CONFIG REQUIRED) 10 | include_directories(${gaia_INCLUDE_DIR}) 11 | 12 | add_executable(${PROJ_NAME} src/main.cpp) 13 | 14 | set(THREADS_PREFER_PTHREAD_FLAG ON) 15 | find_package(Threads REQUIRED) 16 | target_link_libraries(${PROJ_NAME} gaia::gaia) 17 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) 18 | 19 | set(PROJ_TARGET "run") 20 | add_custom_target(${PROJ_TARGET} 21 | COMMAND ${PROJ_NAME} 22 | DEPENDS ${PROJ_NAME} 23 | WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} 24 | ) 25 | -------------------------------------------------------------------------------- /.vscode/conan-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": [ 3 | { 4 | "name": "test_package", 5 | "conanFile": "${workspaceFolder}/pkg/conan/test_package/conanfile.py", 6 | "profile": "default", 7 | "installArg": "", 8 | "buildArg": "", 9 | "createUser": "disroop", 10 | "createChannel": "development", 11 | "createArg": "" 12 | }, 13 | { 14 | "name": "conan", 15 | "conanFile": "${workspaceFolder}/pkg/conan/conanfile.py", 16 | "profile": "default", 17 | "installArg": "", 18 | "buildArg": "", 19 | "createUser": "disroop", 20 | "createChannel": "development", 21 | "createArg": "" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /include/gaia/ecs/component_getter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/ecs/chunk.h" 7 | #include "gaia/ecs/component.h" 8 | 9 | namespace gaia { 10 | namespace ecs { 11 | struct ComponentGetter { 12 | const Chunk* m_pChunk; 13 | uint16_t m_row; 14 | 15 | //! Returns the value stored in the component @a T on entity. 16 | //! \tparam T Component 17 | //! \return Value stored in the component. 18 | template 19 | GAIA_NODISCARD decltype(auto) get() const { 20 | verify_comp(); 21 | 22 | if constexpr (entity_kind_v == EntityKind::EK_Gen) 23 | return m_pChunk->template get(m_row); 24 | else 25 | return m_pChunk->template get(); 26 | } 27 | }; 28 | } // namespace ecs 29 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/command_buffer_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gaia { 4 | namespace ecs { 5 | class World; 6 | 7 | namespace detail { 8 | template 9 | class CommandBuffer; 10 | } 11 | struct AccessContextST; 12 | struct AccessContextMT; 13 | using CommandBufferST = detail::CommandBuffer; 14 | using CommandBufferMT = detail::CommandBuffer; 15 | 16 | CommandBufferST* cmd_buffer_st_create(World& world); 17 | void cmd_buffer_destroy(CommandBufferST& cmdBuffer); 18 | void cmd_buffer_commit(CommandBufferST& cmdBuffer); 19 | 20 | CommandBufferMT* cmd_buffer_mt_create(World& world); 21 | void cmd_buffer_destroy(CommandBufferMT& cmdBuffer); 22 | void cmd_buffer_commit(CommandBufferMT& cmdBuffer); 23 | } // namespace ecs 24 | } // namespace gaia 25 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 2 | portability-*, 3 | performance-*, 4 | -performance-no-int-to-ptr, 5 | readability-*, 6 | -readability-identifier-*, 7 | -readability-braces-*, 8 | -readability-isolate-declaration, 9 | -readability-magic-numbers, 10 | -readability-function-cognitive-complexity, 11 | -readability-uppercase-*, 12 | -readability-static-accessed-through-instance, 13 | bugprone-*, 14 | -bugprone-easily-swappable-parameters, 15 | -bugprone-lambda-function-name, 16 | clang-analyzer-*, 17 | cppcoreguidelines-*, 18 | -cppcoreguidelines-macro-usage, 19 | -cppcoreguidelines-owning-memory, 20 | -cppcoreguidelines-no-malloc, 21 | -cppcoreguidelines-avoid-*, 22 | -cppcoreguidelines-pro-type-*, 23 | -cppcoreguidelines-pro-bounds-*, 24 | -cppcoreguidelines-non-private-member-variables-in-classes 25 | -------------------------------------------------------------------------------- /include/gaia/ecs/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | namespace gaia { 7 | namespace ecs { 8 | GAIA_NODISCARD inline bool version_changed(uint32_t changeVersion, uint32_t requiredVersion) { 9 | // When a system runs for the first time, everything is considered changed. 10 | if GAIA_UNLIKELY (requiredVersion == 0U) 11 | return true; 12 | 13 | // Supporting wrap-around for version numbers. ChangeVersion must be 14 | // bigger than requiredVersion (never detect change of something the 15 | // system itself changed). 16 | return (int)(changeVersion - requiredVersion) > 0; 17 | } 18 | 19 | inline void update_version(uint32_t& version) { 20 | ++version; 21 | // Handle wrap-around, 0 is reserved for systems that have never run. 22 | if GAIA_UNLIKELY (version == 0U) 23 | ++version; 24 | } 25 | } // namespace ecs 26 | } // namespace gaia 27 | -------------------------------------------------------------------------------- /pkg/conan/test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.tools.build import can_run 3 | from conan.tools.cmake import CMake, cmake_layout 4 | import os 5 | 6 | class TestPackageConan(ConanFile): 7 | settings = "os", "arch", "compiler", "build_type" 8 | generators = "CMakeDeps", "CMakeToolchain" 9 | test_type = "explicit" 10 | 11 | def config_options(self): 12 | self.conf.define("tools.cmake.cmaketoolchain:user_presets", "") 13 | 14 | def layout(self): 15 | # Force build in temp 16 | cmake_layout(self, build_folder="$TMP") 17 | 18 | def requirements(self): 19 | self.requires(self.tested_reference_str) 20 | 21 | def build(self): 22 | cmake = CMake(self) 23 | cmake.configure() 24 | cmake.build() 25 | 26 | def test(self): 27 | if can_run(self): 28 | bin_path = os.path.join(self.cpp.build.bindirs[0], "test_package") 29 | self.run(bin_path, env="conanrun") -------------------------------------------------------------------------------- /vm/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | imagename="gaiaecs-linux-builder" 4 | imagename_tmp="${imagename}tmp" 5 | 6 | # Stop running container if it exists 7 | docker stop -t 0 ${imagename} 2>/dev/null || true 8 | docker rm ${imagename} 2>/dev/null || true 9 | 10 | # Decide which Dockerfile to use based on CPU architecture 11 | CURRENT_PLATFORM=$(uname -p) 12 | if [[ "$CURRENT_PLATFORM" =~ i[3-6]86|x86_64 ]]; then 13 | dockerfile="amd64.Dockerfile" 14 | else 15 | dockerfile="Dockerfile" 16 | fi 17 | 18 | # Build the image 19 | docker build --file "${dockerfile}" --tag "${imagename}" . 20 | 21 | # Create a volume 22 | docker volume create ${imagename_tmp} 2>/dev/null || true 23 | 24 | # Run the container 25 | docker run -p 2022:22 \ 26 | --rm \ 27 | -it \ 28 | --privileged \ 29 | --name "${imagename}" \ 30 | --mount type=volume,source=${imagename_tmp},target=/work-output \ 31 | --mount type=bind,source=$(pwd)/..,target=/gaia-ecs \ 32 | --workdir /gaia-ecs/vm \ 33 | "${imagename}" bash -------------------------------------------------------------------------------- /vm/setup_podman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | podman machine start 2>/dev/null || true 4 | 5 | imagename="gaiaecs-linux-builder" 6 | imagename_tmp=${imagename}"tmp" 7 | 8 | # Stop running container if it exists 9 | podman stop -t 0 ${imagename} 2>/dev/null || true 10 | podman rm ${imagename} 2>/dev/null || true 11 | 12 | # Decide which Dockerfile to use based on CPU architecture 13 | CURRENT_PLATFORM=$(uname -p) 14 | if [[ "$CURRENT_PLATFORM" =~ i[3-6]86|x86_64 ]]; then 15 | dockerfile="amd64.Dockerfile" 16 | else 17 | dockerfile="Dockerfile" 18 | fi 19 | 20 | # Build the image 21 | podman build --file ${dockerfile} --tag ${imagename} . 22 | 23 | # Create volume if needed 24 | podman volume create ${imagename_tmp} 2>/dev/null || true 25 | 26 | # Run the container 27 | podman run -p 2022:22 \ 28 | --rm \ 29 | -it \ 30 | --privileged \ 31 | --name ${imagename} \ 32 | --mount type=volume,source=${imagename_tmp},target=/work-output \ 33 | --mount type=bind,source=$(pwd)/..,target=/gaia-ecs \ 34 | --workdir /gaia-ecs/vm \ 35 | ${imagename} bash -------------------------------------------------------------------------------- /include/gaia/mt/spinlock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | namespace gaia { 7 | namespace mt { 8 | class GAIA_API SpinLock final { 9 | std::atomic_int32_t m_value{}; 10 | 11 | public: 12 | SpinLock() = default; 13 | SpinLock(const SpinLock&) = delete; 14 | SpinLock& operator=(const SpinLock&) = delete; 15 | 16 | bool try_lock() { 17 | // Attempt to acquire the lock without waiting 18 | return 0 == m_value.exchange(1, std::memory_order_acquire); 19 | } 20 | 21 | void lock() { 22 | while (true) { 23 | // The value has been changed, we successfully entered the lock 24 | if (0 == m_value.exchange(1, std::memory_order_acquire)) 25 | break; 26 | 27 | // Yield until unlocked 28 | while (m_value.load(std::memory_order_relaxed) != 0) 29 | GAIA_YIELD_CPU; 30 | } 31 | } 32 | 33 | void unlock() { 34 | // Release the lock 35 | m_value.store(0, std::memory_order_release); 36 | } 37 | }; 38 | } // namespace mt 39 | } // namespace gaia -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Richard Biely 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Gaia-ECS 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Is your feature request related to a problem? Please describe 11 | 12 | A clear and concise description of what the problem is.
13 | For example, `After adding a component to an entity I always get a crash`. 14 | 15 | ## Describe the solution you'd like 16 | 17 | A clear and concise description of what you want to happen.
18 | For example, `I'm expecting that after deleting an entity no crash happens.` 19 | 20 | ## Describe alternatives you've considered 21 | 22 | A clear and concise description of any alternative solutions or features you've considered.
23 | For example, `Rather than the program crashing silently, I'd expect an assert informing me that I did something that is not allowed.` 24 | 25 | ## Additional context 26 | 27 | Add any other context or screenshots about the feature request here.
28 | For example, `This issue seems to happen only on Friday nights when I'm very tired, and the moon is full. Attaching a picture of my cat's reaction.` 29 | -------------------------------------------------------------------------------- /include/gaia/mem/raw_data_holder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/mem/data_layout_policy.h" 7 | 8 | namespace gaia { 9 | namespace mem { 10 | namespace detail { 11 | template 12 | struct raw_data_holder { 13 | static_assert(Size > 0); 14 | 15 | GAIA_ALIGNAS(Alignment) uint8_t data[Size]; 16 | 17 | constexpr operator uint8_t*() noexcept { 18 | return &data[0]; 19 | } 20 | 21 | constexpr operator const uint8_t*() const noexcept { 22 | return &data[0]; 23 | } 24 | }; 25 | 26 | template 27 | struct raw_data_holder { 28 | static_assert(Size > 0); 29 | 30 | uint8_t data[Size]; 31 | 32 | constexpr operator uint8_t*() noexcept { 33 | return &data[0]; 34 | } 35 | 36 | constexpr operator const uint8_t*() const noexcept { 37 | return &data[0]; 38 | } 39 | }; 40 | } // namespace detail 41 | 42 | template 43 | using raw_data_holder = detail::raw_data_holder::Alignment>; 44 | } // namespace mem 45 | } // namespace gaia -------------------------------------------------------------------------------- /src/examples/example2/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace gaia; 4 | 5 | struct Position { 6 | float x, y, z; 7 | }; 8 | struct Acceleration { 9 | float x, y, z; 10 | }; 11 | 12 | void MoveSystem(ecs::World& w, float dt) { 13 | auto q = w.query().all().all(); 14 | q.each([&](Position& p, const Acceleration& a) { 15 | p.x += a.x * dt; 16 | p.y += a.y * dt; 17 | p.z += a.z * dt; 18 | }); 19 | } 20 | 21 | int main() { 22 | ecs::World w; 23 | 24 | constexpr uint32_t N = 100; 25 | 26 | // Create entities with position and acceleration 27 | auto e = w.add(); 28 | w.add(e, {}); 29 | w.add(e, {0, 0, 1}); 30 | GAIA_FOR(N) { 31 | [[maybe_unused]] auto newEntity = w.copy(e); 32 | } 33 | 34 | // Record the original position 35 | auto p0 = w.get(e); 36 | 37 | // Move until a key is hit 38 | constexpr uint32_t GameLoops = 10; 39 | GAIA_FOR(GameLoops) { 40 | float dt = 0.01f; // simulate 100 FPS 41 | MoveSystem(w, dt); 42 | } 43 | 44 | auto p1 = w.get(e); 45 | GAIA_LOG_N("Entity 0 moved from [%.2f,%.2f,%.2f] to [%.2f,%.2f,%.2f]", p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen REQUIRED) 2 | 3 | if(DOXYGEN_FOUND) 4 | include(FetchContent) 5 | 6 | FetchContent_Declare( 7 | doxygen-awesome-css 8 | GIT_REPOSITORY https://github.com/jothepro/doxygen-awesome-css 9 | GIT_TAG main 10 | GIT_SHALLOW 1 11 | ) 12 | 13 | FetchContent_MakeAvailable(doxygen-awesome-css) 14 | 15 | set(DOXY_CSS_DIRECTORY ${doxygen-awesome-css_SOURCE_DIR}) 16 | set(DOXY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) 17 | 18 | message("DOXY_CSS_DIRECTORY: ${doxygen-awesome-css_SOURCE_DIR}") 19 | message("DOXY_OUTPUT_DIRECTORY: ${DOXY_OUTPUT_DIRECTORY}") 20 | 21 | configure_file(Doxyfile ${DOXY_OUTPUT_DIRECTORY}/Doxyfile @ONLY) 22 | 23 | add_custom_target(docs ALL 24 | COMMENT "Generating Doxygen documentation in ${DOXY_OUTPUT_DIRECTORY}/docs using ${DOXYGEN_EXECUTABLE}" 25 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 26 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXY_OUTPUT_DIRECTORY}/Doxyfile 27 | VERBATIM 28 | ) 29 | 30 | if(GAIA_INSTALL) 31 | install( 32 | DIRECTORY ${DOXY_OUTPUT_DIRECTORY}/html 33 | DESTINATION share/${PROJECT_NAME}-${PROJECT_VERSION}/ 34 | ) 35 | endif() 36 | else() 37 | message(WARNING "Doxygen not found. Documentation can't be generated") 38 | endif() -------------------------------------------------------------------------------- /vm/setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | set "imagename=gaiaecs-linux-builder" 5 | set "imagename_tmp=%imagename%-tmp" 6 | 7 | :: Stop running container if it exists 8 | docker stop -t 0 %imagename% >nul 2>&1 9 | docker rm %imagename% >nul 2>&1 10 | 11 | :: Detect platform architecture 12 | set "currach=%PROCESSOR_ARCHITECTURE%" 13 | 14 | :: Build image based on architecture 15 | if "%currach%"=="ARM64" ( 16 | :: Generic Dockerfile 17 | docker build --file Dockerfile --tag %imagename% . 18 | ) else if "%currach%"=="AMD64" ( 19 | :: Intel-specific Dockerfile 20 | docker build --file amd64.Dockerfile --tag %imagename% . 21 | ) else if "%currach%"=="x86" ( 22 | :: Intel-specific Dockerfile 23 | docker build --file amd64.Dockerfile --tag %imagename% . 24 | ) else ( 25 | echo Unknown platform architecture: %currach% 26 | exit /b 1 27 | ) 28 | 29 | :: Create volume if it doesn't exist 30 | docker volume create %imagename_tmp% >nul 2>&1 31 | 32 | :: Get current directory of the script 33 | set "currdir=%~dp0" 34 | 35 | :: Run the container 36 | docker run -p 2022:22 --rm -it --privileged ^ 37 | --name %imagename% ^ 38 | --mount type=volume,source=%imagename_tmp%,target=/work-output ^ 39 | --mount type=bind,source=%currdir%..,target=/gaia-ecs ^ 40 | --workdir /gaia-ecs/vm ^ 41 | %imagename% bash 42 | 43 | pause 44 | endlocal -------------------------------------------------------------------------------- /include/gaia/ecs/ser_binary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include "gaia/mem/mem_alloc.h" 5 | #include "gaia/ser/ser_buffer_binary.h" 6 | #include "gaia/ser/ser_rt.h" 7 | 8 | namespace gaia { 9 | namespace ecs { 10 | class BinarySerializer: public ser::ISerializer { 11 | ser::ser_buffer_binary m_buffer; 12 | 13 | //! Makes sure data is aligned 14 | void align(uint32_t size, ser::serialization_type_id id) { 15 | const auto pos = m_buffer.tell(); 16 | const auto posAligned = mem::align(pos, ser::serialization_type_size(id, size)); 17 | const auto offset = posAligned - pos; 18 | m_buffer.reserve(offset + size); 19 | m_buffer.skip(offset); 20 | } 21 | 22 | public: 23 | void save_raw(const void* src, uint32_t size, ser::serialization_type_id id) override { 24 | align(size, id); 25 | m_buffer.save_raw((const char*)src, size, id); 26 | } 27 | 28 | void load_raw(void* src, uint32_t size, ser::serialization_type_id id) override { 29 | align(size, id); 30 | m_buffer.load_raw((char*)src, size, id); 31 | } 32 | 33 | const char* data() const override { 34 | return (const char*)m_buffer.data(); 35 | } 36 | 37 | void reset() override { 38 | m_buffer.reset(); 39 | } 40 | 41 | uint32_t tell() const override { 42 | return m_buffer.tell(); 43 | } 44 | 45 | uint32_t bytes() const override { 46 | return m_buffer.bytes(); 47 | } 48 | 49 | void seek(uint32_t pos) override { 50 | m_buffer.seek(pos); 51 | } 52 | }; 53 | } // namespace ecs 54 | } // namespace gaia 55 | -------------------------------------------------------------------------------- /src/examples/example_wasm/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gaia; 5 | 6 | // How to test: 7 | // 1) emcmake cmake -S . -B build-wasm 8 | // 2) cmake --build build-wasm/src/examples/example_wasm -j 9 | // 3) emrun --no_browser --port 8080 --serve_root build-wasm/src/examples/example_wasm 10 | // or emrun --browser chrome --port 8080 --serve_root build-wasm/src/examples/example_wasm gaia_example_wasm.html 11 | // 4) http://localhost:8080/gaia_example_wasm.html 12 | 13 | struct Position { 14 | float x, y, z; 15 | }; 16 | struct Acceleration { 17 | float x, y, z; 18 | }; 19 | 20 | void MoveSystem(ecs::World& w, float dt) { 21 | auto q = w.query().all().all(); 22 | q.each([&](Position& p, const Acceleration& a) { 23 | p.x += a.x * dt; 24 | p.y += a.y * dt; 25 | p.z += a.z * dt; 26 | }); 27 | } 28 | 29 | int main() { 30 | ecs::World w; 31 | 32 | constexpr uint32_t N = 1'500; 33 | 34 | // Create entities with position and acceleration 35 | auto e = w.add(); 36 | w.add(e, {10,15,20}); 37 | w.add(e, {0, 0, 1}); 38 | GAIA_FOR(N) { 39 | [[maybe_unused]] auto newEntity = w.copy(e); 40 | } 41 | 42 | // Record the original position 43 | auto p0 = w.get(e); 44 | 45 | // Move until a key is hit 46 | constexpr uint32_t GameLoops = 1'000; 47 | GAIA_FOR(GameLoops) { 48 | float dt = 0.01f; // simulate 100 FPS 49 | MoveSystem(w, dt); 50 | } 51 | 52 | auto p1 = w.get(e); 53 | GAIA_LOG_N("Entity 0 moved from [%.2f,%.2f,%.2f] to [%.2f,%.2f,%.2f]", p0.x, p0.y, p0.z, p1.x, p1.y, p1.z); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /include/gaia/mt/semaphore_fast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/config/config.h" 4 | 5 | #include 6 | 7 | #include "gaia/mt/semaphore.h" 8 | 9 | namespace gaia { 10 | namespace mt { 11 | //! An optimized version of Semaphore that avoids expensive system calls when the counter is greater than 0. 12 | class GAIA_API SemaphoreFast final { 13 | Semaphore m_sem; 14 | std::atomic_int32_t m_cnt; 15 | 16 | SemaphoreFast(SemaphoreFast&&) = delete; 17 | SemaphoreFast(const SemaphoreFast&) = delete; 18 | SemaphoreFast& operator=(SemaphoreFast&&) = delete; 19 | SemaphoreFast& operator=(const SemaphoreFast&) = delete; 20 | 21 | public: 22 | explicit SemaphoreFast(int32_t count = 0): m_sem(count), m_cnt(0) {} 23 | ~SemaphoreFast() = default; 24 | 25 | //! Increments semaphore count by the specified amount. 26 | void release(int32_t count = 1) { 27 | const int32_t prevCount = m_cnt.fetch_add(count, std::memory_order_release); 28 | int32_t toRelease = -prevCount; 29 | if (count < toRelease) 30 | toRelease = count; 31 | 32 | if (toRelease > 0) 33 | m_sem.release(toRelease); 34 | } 35 | 36 | //! Decrements semaphore count by 1. 37 | //! If the count is already 0, it waits indefinitely until semaphore count is incremented, 38 | //! then decrements and returns. Returns false when an error occurs, otherwise returns true. 39 | bool wait() { 40 | const int32_t oldCount = m_cnt.fetch_sub(1, std::memory_order_acquire); 41 | bool result = true; 42 | if (oldCount <= 0) 43 | result = m_sem.wait(); 44 | 45 | return result; 46 | } 47 | }; 48 | } // namespace mt 49 | } // namespace gaia -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths: 7 | - '**.cpp' 8 | - '**.h' 9 | - '**.hpp' 10 | - 'docs/**' 11 | - 'README.md' 12 | - 'Doxyfile' 13 | - 'docs.yml' 14 | pull_request: 15 | branches: [ main ] 16 | paths: 17 | - '**.cpp' 18 | - '**.h' 19 | - '**.hpp' 20 | - 'docs/**' 21 | - 'README.md' 22 | - 'Doxyfile' 23 | - 'docs.yml' 24 | workflow_dispatch: 25 | 26 | permissions: 27 | contents: write 28 | pages: write 29 | id-token: write 30 | 31 | # Allow one concurrent deployment 32 | concurrency: 33 | group: "pages" 34 | cancel-in-progress: true 35 | 36 | jobs: 37 | deploy: 38 | environment: 39 | name: github-pages 40 | url: ${{ steps.deployment.outputs.page_url }} 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v5 48 | 49 | - name: Install Doxygen 50 | run: sudo apt-get update && sudo apt-get install -y doxygen 51 | 52 | - name: Setup CMake 53 | uses: lukka/get-cmake@latest 54 | 55 | - name: Configure CMake 56 | run: cmake -B build -S . -DGAIA_GENERATE_DOCS=ON 57 | 58 | - name: Generate documentation 59 | run: cmake --build build --target docs 60 | 61 | - name: Deploy to GitHub Pages 62 | uses: peaceiris/actions-gh-pages@v4 63 | with: 64 | github_token: ${{ secrets.GITHUB_TOKEN }} 65 | publish_dir: build/docs/html 66 | force_orphan: true 67 | -------------------------------------------------------------------------------- /vm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV LANG=C.UTF-8 4 | ARG DEBIAN_FRONTEND=noninteractive 5 | 6 | #################################################################################################### 7 | # Use bash for shell 8 | #################################################################################################### 9 | CMD ["/bin/bash"] 10 | 11 | #################################################################################################### 12 | # Install basic dependencies 13 | #################################################################################################### 14 | RUN apt update && apt install -y --no-install-recommends \ 15 | software-properties-common \ 16 | clang \ 17 | llvm \ 18 | g++ \ 19 | gcc \ 20 | gdb \ 21 | make \ 22 | cmake \ 23 | tzdata \ 24 | git \ 25 | ninja-build \ 26 | neovim \ 27 | valgrind \ 28 | openssh-server 29 | 30 | #################################################################################################### 31 | # Purge the apt list 32 | #################################################################################################### 33 | RUN rm -rf /var/lib/apt/lists/* 34 | 35 | #################################################################################################### 36 | # Set up ssh 37 | #################################################################################################### 38 | RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \ 39 | && echo 'root:docker' | chpasswd 40 | 41 | EXPOSE 22 42 | ENTRYPOINT ["sh", "-c", "service ssh restart && exec bash"] 43 | -------------------------------------------------------------------------------- /vm/amd64.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amd64/ubuntu:22.04 2 | 3 | ENV LANG=C.UTF-8 4 | ARG DEBIAN_FRONTEND=noninteractive 5 | 6 | #################################################################################################### 7 | # Use bash for shell 8 | #################################################################################################### 9 | CMD ["/bin/bash"] 10 | 11 | #################################################################################################### 12 | # Install basic dependencies 13 | #################################################################################################### 14 | RUN apt update && apt install -y --no-install-recommends \ 15 | software-properties-common \ 16 | clang \ 17 | llvm \ 18 | g++ \ 19 | gcc \ 20 | gdb \ 21 | make \ 22 | cmake \ 23 | tzdata \ 24 | git \ 25 | ninja-build \ 26 | neovim \ 27 | valgrind \ 28 | openssh-server 29 | 30 | #################################################################################################### 31 | # Purge the apt list 32 | #################################################################################################### 33 | RUN rm -rf /var/lib/apt/lists/* 34 | 35 | #################################################################################################### 36 | # Set up ssh 37 | #################################################################################################### 38 | RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \ 39 | && echo 'root:docker' | chpasswd 40 | 41 | EXPOSE 22 42 | ENTRYPOINT ["sh", "-c", "service ssh restart && exec bash"] 43 | -------------------------------------------------------------------------------- /include/gaia/core/dyn_singleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gaia { 4 | namespace core { 5 | //! Gaia-ECS is a header-only library which means we want to avoid using global 6 | //! static variables because they would get copied to each translation units. 7 | //! At the same time the goal is for users to not see any memory allocation used 8 | //! by the library. Therefore, the only solution is a static variable with local 9 | //! scope. 10 | //! 11 | //! Being a static variable with local scope which means the singleton is guaranteed 12 | //! to be younger than its caller. Because static variables are released in the reverse 13 | //! order in which they are created, if used with a static World it would mean we first 14 | //! release the singleton and only then proceed with the world itself. As a result, in 15 | //! its destructor the world could access memory that has already been released. 16 | //! 17 | //! Instead, we let the singleton allocate the object on the heap and once singleton's 18 | //! destructor is called we tell the internal object it should destroy itself. This way 19 | //! there are no memory leaks or access-after-freed issues on app exit reported. 20 | template 21 | class dyn_singleton final { 22 | T* m_obj = new T(); 23 | 24 | dyn_singleton() = default; 25 | 26 | public: 27 | static T& get() noexcept { 28 | static dyn_singleton singleton; 29 | return *singleton.m_obj; 30 | } 31 | 32 | dyn_singleton(dyn_singleton&& world) = delete; 33 | dyn_singleton(const dyn_singleton& world) = delete; 34 | dyn_singleton& operator=(dyn_singleton&&) = delete; 35 | dyn_singleton& operator=(const dyn_singleton&) = delete; 36 | 37 | ~dyn_singleton() { 38 | get().done(); 39 | } 40 | }; 41 | } // namespace core 42 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/core/func.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | namespace gaia { 7 | namespace detail { 8 | template 9 | struct is_reference_wrapper: std::false_type {}; 10 | template 11 | struct is_reference_wrapper>: std::true_type {}; 12 | 13 | template 14 | constexpr decltype(auto) invoke_memptr(Pointed C::* member, Object&& object, Args&&... args) { 15 | using object_t = std::decay_t; 16 | constexpr bool is_member_function = std::is_function_v; 17 | constexpr bool is_wrapped = is_reference_wrapper::value; 18 | constexpr bool is_derived_object = std::is_same_v || std::is_base_of_v; 19 | 20 | if constexpr (is_member_function) { 21 | if constexpr (is_derived_object) 22 | return (GAIA_FWD(object).*member)(GAIA_FWD(args)...); 23 | else if constexpr (is_wrapped) 24 | return (object.get().*member)(GAIA_FWD(args)...); 25 | else 26 | return ((*GAIA_FWD(object)).*member)(GAIA_FWD(args)...); 27 | } else { 28 | static_assert(std::is_object_v && sizeof...(args) == 0); 29 | if constexpr (is_derived_object) 30 | return GAIA_FWD(object).*member; 31 | else if constexpr (is_wrapped) 32 | return object.get().*member; 33 | else 34 | return (*GAIA_FWD(object)).*member; 35 | } 36 | } 37 | } // namespace detail 38 | 39 | template 40 | constexpr decltype(auto) invoke(F&& f, Args&&... args) noexcept(std::is_nothrow_invocable_v) { 41 | if constexpr (std::is_member_pointer_v>) 42 | return detail::invoke_memptr(f, GAIA_FWD(args)...); 43 | else 44 | return GAIA_FWD(f)(GAIA_FWD(args)...); 45 | } 46 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia.h: -------------------------------------------------------------------------------- 1 | #include "gaia/config/config.h" 2 | #include "gaia/config/profiler.h" 3 | 4 | #include "gaia/core/bit_utils.h" 5 | #include "gaia/core/hashing_policy.h" 6 | #include "gaia/core/iterator.h" 7 | #include "gaia/core/span.h" 8 | #include "gaia/core/utility.h" 9 | 10 | #include "gaia/meta/reflection.h" 11 | #include "gaia/meta/type_info.h" 12 | 13 | #include "gaia/mem/data_layout_policy.h" 14 | #include "gaia/mem/mem_alloc.h" 15 | #include "gaia/mem/mem_sani.h" 16 | #include "gaia/mem/mem_utils.h" 17 | #include "gaia/mem/raw_data_holder.h" 18 | #include "gaia/mem/stack_allocator.h" 19 | 20 | #include "gaia/cnt/bitset.h" 21 | #include "gaia/cnt/bitset_iterator.h" 22 | #include "gaia/cnt/darray.h" 23 | #include "gaia/cnt/darray_ext.h" 24 | #include "gaia/cnt/darray_ext_soa.h" 25 | #include "gaia/cnt/darray_soa.h" 26 | #include "gaia/cnt/dbitset.h" 27 | #include "gaia/cnt/fwd_llist.h" 28 | #include "gaia/cnt/ilist.h" 29 | #include "gaia/cnt/map.h" 30 | #include "gaia/cnt/paged_storage.h" 31 | #include "gaia/cnt/sarray.h" 32 | #include "gaia/cnt/sarray_ext.h" 33 | #include "gaia/cnt/sarray_ext_soa.h" 34 | #include "gaia/cnt/sarray_soa.h" 35 | #include "gaia/cnt/set.h" 36 | #include "gaia/cnt/sparse_storage.h" 37 | #include "gaia/cnt/sringbuffer.h" 38 | 39 | #include "gaia/util/logging.h" 40 | #include "gaia/util/signal.h" 41 | 42 | #include "gaia/ser/ser_ct.h" 43 | #include "gaia/ser/ser_rt.h" 44 | 45 | #include "gaia/mt/threadpool.h" 46 | 47 | #include "gaia/ecs/archetype.h" 48 | #include "gaia/ecs/chunk.h" 49 | #include "gaia/ecs/chunk_allocator.h" 50 | #include "gaia/ecs/chunk_iterator.h" 51 | #include "gaia/ecs/command_buffer.h" 52 | #include "gaia/ecs/common.h" 53 | #include "gaia/ecs/component.h" 54 | #include "gaia/ecs/component_getter.h" 55 | #include "gaia/ecs/component_setter.h" 56 | #include "gaia/ecs/id.h" 57 | #include "gaia/ecs/query.h" 58 | #include "gaia/ecs/world.h" 59 | -------------------------------------------------------------------------------- /include/gaia/ecs/archetype_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/cnt/darray.h" 7 | #include "gaia/cnt/map.h" 8 | #include "gaia/core/hashing_policy.h" 9 | 10 | namespace gaia { 11 | namespace ecs { 12 | class Archetype; 13 | 14 | using ArchetypeId = uint32_t; 15 | using ArchetypeDArray = cnt::darray; 16 | using ArchetypeIdHash = core::direct_hash_key; 17 | 18 | struct ArchetypeIdHashPair { 19 | ArchetypeId id; 20 | ArchetypeIdHash hash; 21 | 22 | GAIA_NODISCARD bool operator==(ArchetypeIdHashPair other) const { 23 | return id == other.id; 24 | } 25 | GAIA_NODISCARD bool operator!=(ArchetypeIdHashPair other) const { 26 | return id != other.id; 27 | } 28 | }; 29 | 30 | static constexpr ArchetypeId ArchetypeIdBad = (ArchetypeId)-1; 31 | static constexpr ArchetypeIdHashPair ArchetypeIdHashPairBad = {ArchetypeIdBad, {0}}; 32 | 33 | class ArchetypeIdLookupKey final { 34 | public: 35 | using LookupHash = core::direct_hash_key; 36 | 37 | private: 38 | ArchetypeId m_id; 39 | ArchetypeIdHash m_hash; 40 | 41 | public: 42 | GAIA_NODISCARD static LookupHash calc(ArchetypeId id) { 43 | return {static_cast(core::calculate_hash64(id))}; 44 | } 45 | 46 | static constexpr bool IsDirectHashKey = true; 47 | 48 | ArchetypeIdLookupKey(): m_id(0), m_hash({0}) {} 49 | ArchetypeIdLookupKey(ArchetypeIdHashPair pair): m_id(pair.id), m_hash(pair.hash) {} 50 | explicit ArchetypeIdLookupKey(ArchetypeId id, LookupHash hash): m_id(id), m_hash(hash) {} 51 | 52 | GAIA_NODISCARD size_t hash() const { 53 | return (size_t)m_hash.hash; 54 | } 55 | 56 | GAIA_NODISCARD bool operator==(const ArchetypeIdLookupKey& other) const { 57 | // Hash doesn't match we don't have a match. 58 | // Hash collisions are expected to be very unlikely so optimize for this case. 59 | if GAIA_LIKELY (m_hash != other.m_hash) 60 | return false; 61 | 62 | return m_id == other.m_id; 63 | } 64 | }; 65 | 66 | using ArchetypeMapById = cnt::map; 67 | } // namespace ecs 68 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/mt/jobcommon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | // TODO: Currently necessary due to std::function. Replace them! 5 | #include 6 | 7 | #include "gaia/core/utility.h" 8 | #include "gaia/mt/event.h" 9 | #include "gaia/mt/jobqueue.h" 10 | 11 | namespace gaia { 12 | namespace mt { 13 | enum class JobPriority : uint8_t { 14 | //! High priority job. If available it should target the CPU's performance cores. 15 | High = 0, 16 | //! Low priority job. If available it should target the CPU's efficiency cores. 17 | Low = 1 18 | }; 19 | static inline constexpr uint32_t JobPriorityCnt = 2; 20 | 21 | enum JobCreationFlags : uint8_t { 22 | Default = 0, 23 | //! The job is not deleted automatically. Has to be done by the used. 24 | ManualDelete = 0x01, 25 | //! The job can wait for other job (one not set as dependency). 26 | CanWait = 0x02 27 | }; 28 | 29 | struct JobAllocCtx { 30 | JobPriority priority; 31 | }; 32 | 33 | struct Job { 34 | std::function func; 35 | JobPriority priority = JobPriority::High; 36 | JobCreationFlags flags = JobCreationFlags::Default; 37 | }; 38 | 39 | struct JobArgs { 40 | uint32_t idxStart; 41 | uint32_t idxEnd; 42 | }; 43 | 44 | struct JobParallel { 45 | std::function func; 46 | JobPriority priority = JobPriority::High; 47 | }; 48 | 49 | class ThreadPool; 50 | 51 | struct ThreadCtx { 52 | //! Thread pool pointer 53 | ThreadPool* tp; 54 | //! Worker index 55 | uint32_t workerIdx; 56 | //! Job priority 57 | JobPriority prio; 58 | //! Event signaled when a job is executed 59 | Event event; 60 | //! Lock-free work stealing queue for the jobs 61 | JobQueue<512> jobQueue; 62 | 63 | ThreadCtx() = default; 64 | ~ThreadCtx() = default; 65 | 66 | void reset() { 67 | event.reset(); 68 | jobQueue.clear(); 69 | } 70 | 71 | ThreadCtx(const ThreadCtx& other) = delete; 72 | ThreadCtx& operator=(const ThreadCtx& other) = delete; 73 | ThreadCtx(ThreadCtx&& other) = delete; 74 | ThreadCtx& operator=(ThreadCtx&& other) = delete; 75 | }; 76 | } // namespace mt 77 | } // namespace gaia -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 2 4 | TabWidth: 2 5 | UseTab: Always 6 | ColumnLimit: 120 7 | --- 8 | Language: Cpp 9 | UseCRLF: true 10 | DeriveLineEnding: false 11 | DerivePointerAlignment: false 12 | PointerAlignment: Left 13 | AlignAfterOpenBracket: AlwaysBreak 14 | Cpp11BracedListStyle: true 15 | BreakBeforeBraces: Custom 16 | BraceWrapping: 17 | AfterEnum: false 18 | AfterStruct: false 19 | AfterClass: false 20 | AfterUnion: false 21 | AfterExternBlock: false 22 | AfterCaseLabel: false 23 | AfterFunction: false 24 | AfterNamespace: false 25 | AfterControlStatement: Never 26 | BeforeElse: false 27 | #BeforeWhile: false 28 | #BeforeLambdaBody: false 29 | BeforeCatch: false 30 | SplitEmptyFunction: false 31 | SplitEmptyRecord: false 32 | SplitEmptyNamespace: false 33 | IndentBraces: false 34 | AllowShortFunctionsOnASingleLine: Empty 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeTernaryOperators: true 37 | BreakConstructorInitializers: AfterColon 38 | BreakInheritanceList: AfterColon 39 | BreakStringLiterals: true 40 | CompactNamespaces: false 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | FixNamespaceComments: true 43 | IndentCaseLabels: true 44 | #IndentExternBlock: true 45 | IndentGotoLabels: true 46 | IndentPPDirectives: BeforeHash 47 | IndentWrappedFunctionNames: false 48 | #InsertTrailingCommas: None 49 | NamespaceIndentation: All 50 | SortUsingDeclarations: true 51 | SpaceAfterCStyleCast: false 52 | SpaceAfterLogicalNot: false 53 | SpaceAfterTemplateKeyword: true 54 | SpaceBeforeAssignmentOperators: true 55 | SpaceBeforeInheritanceColon: false 56 | SpaceBeforeCtorInitializerColon: false 57 | SpaceBeforeRangeBasedForLoopColon: false 58 | SpaceInEmptyBlock: false 59 | SpaceInEmptyParentheses: false 60 | SpacesInConditionalStatement: false 61 | SpacesInContainerLiterals: false 62 | SpacesInParentheses: false 63 | SpacesInSquareBrackets: false 64 | AlwaysBreakTemplateDeclarations: Yes 65 | AlwaysBreakAfterReturnType: None 66 | AllowShortLoopsOnASingleLine: false 67 | AllowShortIfStatementsOnASingleLine: Never 68 | AllowShortLambdasOnASingleLine: Empty 69 | AlignTrailingComments: false 70 | AlignConsecutiveMacros: false 71 | AllowAllArgumentsOnNextLine: true -------------------------------------------------------------------------------- /.github/workflows/sanitize.yml: -------------------------------------------------------------------------------- 1 | name: sanitize 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.cpp' 7 | - '**.h' 8 | - '**.hpp' 9 | - '**/CMakeLists.txt' 10 | - '**.cmake' 11 | - 'build.yml' 12 | pull_request: 13 | paths: 14 | - '**.cpp' 15 | - '**.h' 16 | - '**.hpp' 17 | - '**/CMakeLists.txt' 18 | - '**.cmake' 19 | - 'build.yml' 20 | workflow_dispatch: 21 | 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | jobs: 27 | build-linux: 28 | timeout-minutes: 10 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | sani_type: [addr, mem] 33 | include: 34 | - sani_type: addr 35 | sani_type_option: "'Address;Undefined'" 36 | - sani_type: mem 37 | sani_type_option: "'MemoryWithOrigins'" 38 | build_type: [RelWithDebInfo, Debug] 39 | target: 40 | - { path: "entity/gaia_perf_entity" } 41 | - { path: "iter/gaia_perf_iter" } 42 | - { path: "duel/gaia_perf_duel" } 43 | - { path: "app/gaia_perf_app" } 44 | - { path: "mt/gaia_perf_mt" } 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: ccache 51 | uses: hendrikmuhs/ccache-action@v1.2 52 | with: 53 | key: ccache-${{runner.os}}-clang-${{matrix.build_type}} 54 | create-symlink: true 55 | 56 | - name: Configure CMake 57 | env: 58 | CC: ccache clang 59 | CXX: ccache clang++ 60 | run: | 61 | cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DGAIA_USE_SANITIZER=${{matrix.sani_type_option}} -DGAIA_BUILD_UNITTEST=OFF -DGAIA_GENERATE_CC=OFF -DGAIA_ECS_CHUNK_ALLOCATOR=OFF -DGAIA_BUILD_BENCHMARK=ON -DGAIA_BUILD_EXAMPLES=ON -S . -B ${{github.workspace}}/build 62 | 63 | - name: Build 64 | env: 65 | CC: ccache clang 66 | CXX: ccache clang++ 67 | run: | 68 | cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} 69 | 70 | - name: Test 71 | working-directory: 72 | run: | 73 | ${{github.workspace}}/build/src/perf/${{matrix.target.path}} -s 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you all who decided to contribute to the `Gaia-ECS` project :)
4 | There are several ways to contribute. Following are a few points helping you on your contribution jurney: 5 | 6 | * Before asking a question, please make sure it has not been answered already by searching 7 | on GitHub under [issues](https://github.com/richardbiely/gaia-ecs/issues). Also, do not 8 | forget to search among the closed ones. If you are unable to find a proper answer, feel 9 | free to [open a new issue](https://github.com/richardbiely/gaia-ecs/issues/new). 10 | 11 | * If you want to fix a typo in the inline documentation or in the README file, if you want 12 | to add some new section or improve these in any ways imaginable, please open a new 13 | [pull request](https://github.com/richardbiely/gaia-ecs/pulls). 14 | 15 | * If you found a bug, please make sure a similar one has not already been reported or answered 16 | by searching on GitHub under [issues](https://github.com/richardbiely/gaia-ecs/issues). 17 | If you are unable to find an open issue addressing your issue, feel free to 18 | [open a new one](https://github.com/richardbiely/gaia-ecs/issues/new). Please, do not forget 19 | to carefully describe how to reproduce the issue and add all the information about the system 20 | on which you are experiencing it and point out the version of `Gaia-ECS` you use (tag or commit). 21 | 22 | * If you found a bug and you wrote a patch to fix it, open a new 23 | [pull request](https://github.com/richardbiely/gaia-ecs/pulls) with your code. Please, also add some tests 24 | to avoid any regressions in the future if possible. 25 | 26 | * If you want to propose a new feature and you know how to code it, please do not a pull request directly. 27 | Rather than that, [create a new issue](https://github.com/richardbiely/gaia-ecs/issues/new) to discuss 28 | your proposal. Other users could be interested in your idea and the discussion that will follow can refine 29 | it further and therefore give us a better solution overall. 30 | 31 | * If you want to request a new feature or a change which is not targeted for the open-source audience, you 32 | can reach out to me via the email address which can be found on [my profile page](https://github.com/richardbiely) 33 | and we can discuss hiring me. 34 | -------------------------------------------------------------------------------- /include/gaia/core/bit_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/core/span.h" 7 | 8 | namespace gaia { 9 | namespace core { 10 | template 11 | struct bit_view { 12 | static constexpr uint32_t MaxValue = (1 << BlockBits) - 1; 13 | 14 | std::span m_data; 15 | 16 | void set(uint32_t bitPosition, uint8_t value) noexcept { 17 | GAIA_ASSERT(bitPosition < (m_data.size() * 8)); 18 | GAIA_ASSERT(value <= MaxValue); 19 | 20 | const uint32_t idxByte = bitPosition / 8; 21 | const uint32_t idxBit = bitPosition % 8; 22 | 23 | const uint32_t mask = ~(MaxValue << idxBit); 24 | m_data[idxByte] = (uint8_t)(((uint32_t)m_data[idxByte] & mask) | ((uint32_t)value << idxBit)); 25 | 26 | const bool overlaps = idxBit + BlockBits > 8; 27 | if (overlaps) { 28 | // Value spans over two bytes 29 | const uint32_t shift2 = 8U - idxBit; 30 | const uint32_t mask2 = ~(MaxValue >> shift2); 31 | m_data[idxByte + 1] = (uint8_t)(((uint32_t)m_data[idxByte + 1] & mask2) | ((uint32_t)value >> shift2)); 32 | } 33 | } 34 | 35 | uint8_t get(uint32_t bitPosition) const noexcept { 36 | GAIA_ASSERT(bitPosition < (m_data.size() * 8)); 37 | 38 | const uint32_t idxByte = bitPosition / 8; 39 | const uint32_t idxBit = bitPosition % 8; 40 | 41 | const uint8_t byte1 = (m_data[idxByte] >> idxBit) & MaxValue; 42 | 43 | const bool overlaps = idxBit + BlockBits > 8; 44 | if (overlaps) { 45 | // Value spans over two bytes 46 | const uint32_t shift2 = uint8_t(8U - idxBit); 47 | const uint32_t mask2 = MaxValue >> shift2; 48 | const uint8_t byte2 = uint8_t(((uint32_t)m_data[idxByte + 1] & mask2) << shift2); 49 | return byte1 | byte2; 50 | } 51 | 52 | return byte1; 53 | } 54 | }; 55 | 56 | template 57 | inline auto swap_bits(T& mask, uint32_t left, uint32_t right) { 58 | // Swap the bits in the read-write mask 59 | const uint32_t b0 = (mask >> left) & 1U; 60 | const uint32_t b1 = (mask >> right) & 1U; 61 | // XOR the two bits 62 | const uint32_t bxor = b0 ^ b1; 63 | // Put the XOR bits back to their original positions 64 | const uint32_t m = (bxor << left) | (bxor << right); 65 | // XOR mask with the original one effectively swapping the bits 66 | mask = mask ^ (uint8_t)m; 67 | } 68 | } // namespace core 69 | } // namespace gaia -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.cpp' 7 | - '**.h' 8 | - '**.hpp' 9 | - '**/CMakeLists.txt' 10 | - '**.cmake' 11 | - 'build.yml' 12 | - 'coverage.yml' 13 | pull_request: 14 | paths: 15 | - '**.cpp' 16 | - '**.h' 17 | - '**.hpp' 18 | - '**/CMakeLists.txt' 19 | - '**.cmake' 20 | - 'build.yml' 21 | - 'coverage.yml' 22 | workflow_dispatch: 23 | 24 | jobs: 25 | codecov: 26 | timeout-minutes: 15 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: Install dependencies 33 | run: | 34 | sudo apt update 35 | sudo apt install clang llvm 36 | 37 | - name: ccache 38 | uses: hendrikmuhs/ccache-action@v1.2 39 | with: 40 | key: ccache-${{runner.os}}-clang-Debug 41 | create-symlink: true 42 | 43 | - name: Compile tests 44 | env: 45 | CC: ccache clang 46 | CXX: ccache clang++ 47 | run: | 48 | cmake -DCMAKE_BUILD_TYPE=Debug \ 49 | -DCMAKE_CXX_FLAGS_DEBUG="-fno-inline -fno-default-inline -fno-elide-constructors -fprofile-instr-generate -fcoverage-mapping" \ 50 | -DGAIA_BUILD_UNITTEST=ON \ 51 | -DGAIA_BUILD_EXAMPLES=OFF \ 52 | -DGAIA_BUILD_BENCHMARK=OFF \ 53 | -DGAIA_GENERATE_CC=OFF \ 54 | -S . -B ${{github.workspace}}/build 55 | cmake --build ${{github.workspace}}/build --config Debug 56 | 57 | - name: Run tests 58 | working-directory: ${{github.workspace}}/build/src/test 59 | run: | 60 | LLVM_PROFILE_FILE="${{github.workspace}}/build/src/test/default.profraw" ./gaia_test 61 | 62 | - name: Generate coverage data 63 | working-directory: ${{github.workspace}}/build/src/test 64 | run: | 65 | llvm-profdata merge -sparse default.profraw -o coverage.profdata 66 | llvm-cov export ./gaia_test \ 67 | -instr-profile=coverage.profdata \ 68 | -format=lcov > ${{github.workspace}}/build/coverage.info 69 | llvm-cov report ./gaia_test -instr-profile=coverage.profdata 70 | 71 | - name: Upload coverage to Codecov 72 | uses: codecov/codecov-action@v5 73 | with: 74 | token: ${{secrets.CODECOV_TOKEN}} 75 | files: ${{github.workspace}}/build/coverage.info 76 | name: gaia-ecs 77 | fail_ci_if_error: true 78 | -------------------------------------------------------------------------------- /include/gaia/ecs/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/core/span.h" 7 | #include "gaia/ecs/command_buffer_fwd.h" 8 | #include "gaia/ecs/id_fwd.h" 9 | #include "gaia/ecs/query_fwd.h" 10 | 11 | namespace gaia { 12 | namespace ecs { 13 | class World; 14 | class ComponentCache; 15 | class Archetype; 16 | struct ComponentCacheItem; 17 | struct EntityContainer; 18 | struct Entity; 19 | 20 | // Component API 21 | 22 | const ComponentCache& comp_cache(const World& world); 23 | ComponentCache& comp_cache_mut(World& world); 24 | template 25 | const ComponentCacheItem& comp_cache_add(World& world); 26 | 27 | // Entity API 28 | 29 | const EntityContainer& fetch(const World& world, Entity entity); 30 | EntityContainer& fetch_mut(World& world, Entity entity); 31 | 32 | void del(World& world, Entity entity); 33 | 34 | Entity entity_from_id(const World& world, EntityId id); 35 | 36 | bool valid(const World& world, Entity entity); 37 | 38 | bool is(const World& world, Entity entity, Entity baseEntity); 39 | bool is_base(const World& world, Entity entity); 40 | 41 | Archetype* archetype_from_entity(const World& world, Entity entity); 42 | 43 | const char* entity_name(const World& world, Entity entity); 44 | const char* entity_name(const World& world, EntityId entityId); 45 | 46 | // Traversal API 47 | 48 | template 49 | void as_relations_trav(const World& world, Entity target, Func func); 50 | template 51 | bool as_relations_trav_if(const World& world, Entity target, Func func); 52 | template 53 | void as_targets_trav(const World& world, Entity relation, Func func); 54 | template 55 | bool as_targets_trav_if(const World& world, Entity relation, Func func); 56 | 57 | // Query API 58 | 59 | QuerySerBuffer& query_buffer(World& world, QueryId& serId); 60 | void query_buffer_reset(World& world, QueryId& serId); 61 | 62 | Entity expr_to_entity(const World& world, va_list& args, std::span exprRaw); 63 | 64 | GroupId group_by_func_default(const World& world, const Archetype& archetype, Entity groupBy); 65 | 66 | // Locking API 67 | 68 | void lock(World& world); 69 | void unlock(World& world); 70 | bool locked(const World& world); 71 | 72 | // CommandBuffer API 73 | 74 | CommandBufferST& cmd_buffer_st_get(World& world); 75 | CommandBufferMT& cmd_buffer_mt_get(World& world); 76 | void commit_cmd_buffer_st(World& world); 77 | void commit_cmd_buffer_mt(World& world); 78 | } // namespace ecs 79 | } // namespace gaia -------------------------------------------------------------------------------- /src/examples/example_wasm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_example_wasm") 2 | project(${PROJ_NAME} LANGUAGES C CXX) 3 | 4 | set(SRC_FILE "${CMAKE_CURRENT_LIST_DIR}/src/main.cpp") 5 | 6 | # ----------------------------------------------------------------------------- 7 | # Detect if we're actually using the Emscripten toolchain 8 | # ----------------------------------------------------------------------------- 9 | if(NOT EMSCRIPTEN) 10 | message(STATUS "Skipping ${PROJ_NAME} — not using emcmake (Emscripten toolchain).") 11 | return() 12 | endif() 13 | 14 | # ----------------------------------------------------------------------------- 15 | # Detect if Emscripten is available 16 | # ----------------------------------------------------------------------------- 17 | find_program(EMCC_EXECUTABLE emcc) 18 | 19 | if(NOT EMCC_EXECUTABLE) 20 | message(FATAL_ERROR 21 | "Emscripten not found on this system. " 22 | "Skipping WASM example.\n" 23 | "To build it manually:\n" 24 | " git clone https://github.com/emscripten-core/emsdk.git\n" 25 | " cd emsdk && ./emsdk install latest && ./emsdk activate latest\n" 26 | " source ./emsdk_env.sh\n" 27 | "Then re-run:\n" 28 | " emcmake cmake -S . -B build-wasm && cmake --build build-wasm" 29 | ) 30 | return() 31 | endif() 32 | 33 | # ----------------------------------------------------------------------------- 34 | # Normal WASM build 35 | # ----------------------------------------------------------------------------- 36 | add_executable(${PROJ_NAME} ${SRC_FILE}) 37 | 38 | # target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 39 | target_include_directories(${PROJ_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) 40 | 41 | target_compile_options(${PROJ_NAME} PRIVATE 42 | "-pthread" 43 | "-matomics" 44 | "-mbulk-memory" 45 | ) 46 | 47 | target_link_options(${PROJ_NAME} PRIVATE 48 | "-pthread" 49 | "-sASSERTIONS=1" 50 | "-sMODULARIZE=1" 51 | "-sEXPORT_NAME=GaiaExampleWASM" 52 | "-sEXPORTED_FUNCTIONS=_main,_malloc,_free" 53 | "-sEXPORTED_RUNTIME_METHODS=ccall,cwrap" 54 | 55 | # When using pthreads we need to set environemnt as worker, because threads in WebAssembly are implemented as Web Workers. 56 | "-sUSE_PTHREADS=1" 57 | "-sENVIRONMENT=web,worker" 58 | 59 | # If pthread is not necessary we set environment to web 60 | # "-sENVIRONMENT=web" 61 | 62 | # Memory settings 63 | "-sINITIAL_MEMORY=256MB" 64 | "-sALLOW_MEMORY_GROWTH=0" 65 | ) 66 | 67 | add_custom_command(TARGET ${PROJ_NAME} POST_BUILD 68 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 69 | "${CMAKE_CURRENT_LIST_DIR}/src/gaia_example_wasm.html" 70 | "$/gaia_example_wasm.html" 71 | ) 72 | 73 | message(STATUS "Gaia-ECS WASM example configured for Emscripten.") -------------------------------------------------------------------------------- /vm/setup_podman.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | podman machine start 2>nul 5 | 6 | set "imagename=gaiaecs-linux-builder" 7 | set "imagename_tmp=%imagename%-tmp" 8 | 9 | :: Stop and remove any existing container 10 | podman stop -t 0 %imagename% >nul 2>&1 11 | podman rm %imagename% >nul 2>&1 12 | 13 | :: Detect platform architecture 14 | set "currach=%PROCESSOR_ARCHITECTURE%" 15 | 16 | :: Build image based on architecture 17 | if "%currach%"=="ARM64" ( 18 | :: Generic Dockerfile 19 | podman build --file Dockerfile --tag %imagename% . 20 | ) else if "%currach%"=="AMD64" ( 21 | :: Intel-specific Dockerfile 22 | podman build --file amd64.Dockerfile --tag %imagename% . 23 | ) else if "%currach%"=="x86" ( 24 | :: Intel-specific Dockerfile 25 | podman build --file amd64.Dockerfile --tag %imagename% . 26 | ) else ( 27 | echo Unknown platform architecture: %currach% 28 | exit /b 1 29 | ) 30 | 31 | :: Create volume if it doesn't exist 32 | podman volume create %imagename_tmp% >nul 2>&1 33 | 34 | :: Get current directory of the script 35 | set "currdir=%~dp0" 36 | 37 | :: Run the container 38 | podman run -p 2022:22 --rm -it --privileged ^ 39 | --name %imagename% ^ 40 | --mount type=volume,source=%imagename_tmp%,target=/work-output ^ 41 | --mount type=bind,source=%currdir%..,target=/gaia-ecs ^ 42 | --workdir /gaia-ecs/docker ^ 43 | %imagename% bash 44 | 45 | pause 46 | endlocal@echo off 47 | setlocal 48 | 49 | set "imagename=gaiaecs-linux-builder" 50 | set "imagename_tmp=%imagename%-tmp" 51 | 52 | :: Stop and remove any existing container 53 | podman stop -t 0 %imagename% >nul 2>&1 54 | podman rm %imagename% >nul 2>&1 55 | 56 | :: Detect platform architecture 57 | set "currach=%PROCESSOR_ARCHITECTURE%" 58 | 59 | :: Build image based on architecture 60 | if "%currach%"=="ARM64" ( 61 | :: Generic Dockerfile 62 | podman build --file Dockerfile --tag %imagename% . 63 | ) else if "%currach%"=="AMD64" ( 64 | :: Intel-specific Dockerfile 65 | podman build --file amd64.Dockerfile --tag %imagename% . 66 | ) else if "%currach%"=="x86" ( 67 | :: Intel-specific Dockerfile 68 | podman build --file amd64.Dockerfile --tag %imagename% . 69 | ) else ( 70 | echo Unknown platform architecture: %currach% 71 | exit /b 1 72 | ) 73 | 74 | :: Create volume if it doesn't exist 75 | podman volume create %imagename_tmp% >nul 2>&1 76 | 77 | :: Get current directory of the script 78 | set "currdir=%~dp0" 79 | 80 | :: Run the container 81 | podman run -p 2022:22 --rm -it --privileged ^ 82 | --name %imagename% ^ 83 | --mount type=volume,source=%imagename_tmp%,target=/work-output ^ 84 | --mount type=bind,source=%currdir%..,target=/gaia-ecs ^ 85 | --workdir /gaia-ecs/vm ^ 86 | %imagename% bash 87 | 88 | pause 89 | endlocal -------------------------------------------------------------------------------- /pkg/conan/conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | from conan.errors import ConanInvalidConfiguration 3 | from conan.tools.scm import Version 4 | from conan.tools.build import check_min_cppstd 5 | from conan.tools.files import copy, get 6 | from conan.tools.layout import basic_layout 7 | import os 8 | 9 | required_conan_version = ">=1.52.0" 10 | 11 | class GaiaConan(ConanFile): 12 | name = "gaia-ecs" 13 | version = "0.9.2" 14 | license = "MIT" 15 | package_type = "header-library" 16 | description = "A simple and powerful entity component system (ECS) written in C++17" 17 | homepage = "https://github.com/richardbiely/gaia-ecs" 18 | url = "https://github.com/conan-io/conan-center-index" 19 | topics = ("gamedev", "performance", "entity", "ecs", "dod", "data-oriented-design", "data-oriented", "entity-framework", "entity-component-system", "cpp17") 20 | exports_sources = "LICENSE", "single_include/*" 21 | no_copy_source = True 22 | settings = "os", "arch", "compiler", "build_type" 23 | 24 | @property 25 | def _min_cppstd(self): 26 | return 17 27 | 28 | @property 29 | def _compilers_minimum_version(self): 30 | return { 31 | "Visual Studio": "16", 32 | "msvc": "192", 33 | "gcc": "10", 34 | "clang": "7.0", 35 | "apple-clang": "10.0" 36 | } 37 | 38 | def layout(self): 39 | basic_layout(self, src_folder="src") 40 | 41 | def package_id(self): 42 | self.info.clear() 43 | 44 | def validate(self): 45 | if self.settings.compiler.get_safe("cppstd"): 46 | check_min_cppstd(self, self._min_cppstd) 47 | 48 | minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler), False) 49 | if minimum_version and Version(self.settings.compiler.version) < minimum_version: 50 | raise ConanInvalidConfiguration( 51 | f"{self.ref} requires C++{self._min_cppstd}, which your compiler does not support." 52 | ) 53 | 54 | def source(self): 55 | get(self, **self.conan_data["sources"][self.version], strip_root=True) 56 | 57 | def build(self): 58 | pass 59 | 60 | def package(self): 61 | copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses")) 62 | copy(self, "*", src=os.path.join(self.source_folder, "single_include"), dst=os.path.join(self.package_folder, "include")) 63 | 64 | def package_info(self): 65 | if self.settings.os in ["FreeBSD", "Linux"]: 66 | self.cpp_info.system_libs = ["pthread"] 67 | 68 | self.cpp_info.bindirs = [] 69 | self.cpp_info.libdirs = [] 70 | self.cpp_info.includedirs = ["include"] 71 | self.cpp_info.set_property("cmake_file_name", "gaia") 72 | self.cpp_info.set_property("cmake_target_name", "gaia::gaia") 73 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "inputs": [ 6 | { 7 | "id": "buildType", 8 | "description": "Build configuration to pick", 9 | "type": "pickString", 10 | "options": [ 11 | "Debug", 12 | "RelWithDebInfo" 13 | ], 14 | "default": "Debug" 15 | } 16 | ], 17 | "tasks": [ 18 | { 19 | "label": "Run Linux (Docker)", 20 | "type": "shell", 21 | "options": { 22 | "cwd": "${workspaceFolder}/vm" 23 | }, 24 | "windows": { 25 | "command": "./setup.bat" 26 | }, 27 | "linux": { 28 | "command": "bash", 29 | "args": [ 30 | "setup.sh" 31 | ], 32 | }, 33 | "osx": { 34 | "command": "bash", 35 | "args": [ 36 | "setup.sh" 37 | ], 38 | }, 39 | "group": "build", 40 | "presentation": { 41 | "reveal": "always" 42 | }, 43 | "problemMatcher": [] 44 | }, 45 | { 46 | "label": "Run Linux (Podman)", 47 | "type": "shell", 48 | "options": { 49 | "cwd": "${workspaceFolder}/vm" 50 | }, 51 | "windows": { 52 | "command": "./setup_podman.bat" 53 | }, 54 | "linux": { 55 | "command": "bash", 56 | "args": [ 57 | "setup_podman.sh" 58 | ], 59 | }, 60 | "osx": { 61 | "command": "bash", 62 | "args": [ 63 | "setup_podman.sh" 64 | ], 65 | }, 66 | "group": "build", 67 | "presentation": { 68 | "reveal": "always" 69 | }, 70 | "problemMatcher": [] 71 | }, 72 | { 73 | "label": "Profiler", 74 | "type": "shell", 75 | "command": "${workspaceFolder}/build/${input:buildType}/_deps/tracy-src/profiler/build/unix/tracy-profiler", 76 | "windows": { 77 | "command": "${workspaceFolder}/build/${input:buildType}/_deps/tracy-src/profiler/build/win32/tracy-profiler.exe", 78 | }, 79 | "group": "build", 80 | "presentation": { 81 | "reveal": "always" 82 | }, 83 | "problemMatcher": [] 84 | }, 85 | { 86 | "label": "Merge files into a single header", 87 | "type": "shell", 88 | "options": { 89 | "cwd": "${workspaceFolder}" 90 | }, 91 | "windows": { 92 | "command": "make_single_header.bat" 93 | }, 94 | "linux": { 95 | "command": "bash", 96 | "args": [ 97 | "make_single_header.sh" 98 | ], 99 | }, 100 | "osx": { 101 | "command": "bash", 102 | "args": [ 103 | "make_single_header.sh" 104 | ], 105 | }, 106 | "group": "build", 107 | "presentation": { 108 | "reveal": "always" 109 | }, 110 | "problemMatcher": [] 111 | }, 112 | ] 113 | } -------------------------------------------------------------------------------- /include/gaia/core/hashing_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/core/hashing_policy.h" 7 | #include "gaia/core/utility.h" 8 | 9 | namespace gaia { 10 | namespace core { 11 | template 12 | struct StringLookupKey { 13 | using LookupHash = core::direct_hash_key; 14 | 15 | private: 16 | //! Pointer to the string 17 | const char* m_pStr; 18 | //! Length of the string 19 | uint32_t m_len : 31; 20 | //! 1 - owned (lifetime managed by the framework), 0 - non-owned (lifetime user-managed) 21 | uint32_t m_owned : 1; 22 | //! String hash 23 | LookupHash m_hash; 24 | 25 | static uint32_t len(const char* pStr) { 26 | GAIA_FOR(MaxLen) { 27 | if (pStr[i] == 0) 28 | return i; 29 | } 30 | GAIA_ASSERT2(false, "Only null-terminated strings up to MaxLen characters are supported"); 31 | return BadIndex; 32 | } 33 | 34 | static LookupHash calc(const char* pStr, uint32_t len) { 35 | return {static_cast(core::calculate_hash64(pStr, len))}; 36 | } 37 | 38 | public: 39 | static constexpr bool IsDirectHashKey = true; 40 | 41 | StringLookupKey(): m_pStr(nullptr), m_len(0), m_owned(0), m_hash({0}) {} 42 | 43 | //! Constructor calculating hash from the provided string @a pStr and @a len. 44 | //! \param pStr Pointer to the string 45 | //! \param len Number of characters 46 | //! \param owned True if the string is owned 47 | //! \warning String has to be null-terminated and up to MaxLen characters long. 48 | //! Undefined behavior otherwise. 49 | explicit StringLookupKey(const char* pStr, uint32_t len, uint32_t owned): 50 | m_pStr(pStr), m_len(len), m_owned(owned), m_hash(calc(pStr, len)) {} 51 | 52 | //! Constructor just for setting values 53 | //! \param pStr Pointer to the string 54 | //! \param len Number of characters 55 | //! \param owned True if the string is owned 56 | //! \param hash String hash 57 | explicit StringLookupKey(const char* pStr, uint32_t len, uint32_t owned, LookupHash hash): 58 | m_pStr(pStr), m_len(len), m_owned(owned), m_hash(hash) {} 59 | 60 | const char* str() const { 61 | return m_pStr; 62 | } 63 | 64 | uint32_t len() const { 65 | return m_len; 66 | } 67 | 68 | bool owned() const { 69 | return m_owned == 1; 70 | } 71 | 72 | uint32_t hash() const { 73 | return m_hash.hash; 74 | } 75 | 76 | bool operator==(const StringLookupKey& other) const { 77 | // Hash doesn't match we don't have a match. 78 | // Hash collisions are expected to be very unlikely so optimize for this case. 79 | if GAIA_LIKELY (m_hash != other.m_hash) 80 | return false; 81 | 82 | // Lengths have to match 83 | if (m_len != other.m_len) 84 | return false; 85 | 86 | // Contents have to match 87 | const auto l = m_len; 88 | GAIA_ASSUME(l < MaxLen); 89 | GAIA_FOR(l) { 90 | if (m_pStr[i] != other.m_pStr[i]) 91 | return false; 92 | } 93 | 94 | return true; 95 | } 96 | 97 | bool operator!=(const StringLookupKey& other) const { 98 | return !operator==(other); 99 | } 100 | }; 101 | } // namespace core 102 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/component_setter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/ecs/chunk.h" 7 | #include "gaia/ecs/component.h" 8 | #include "gaia/ecs/component_getter.h" 9 | 10 | namespace gaia { 11 | namespace ecs { 12 | struct ComponentSetter: public ComponentGetter { 13 | //! Returns a mutable reference to component. 14 | //! \tparam T Component or pair 15 | //! \return Reference to data for AoS, or mutable accessor for SoA types 16 | template 17 | decltype(auto) mut() { 18 | return const_cast(m_pChunk)->template set(m_row); 19 | } 20 | 21 | //! Sets the value of the component \tparam T. 22 | //! \tparam T Component or pair 23 | //! \param value Value to set for the component 24 | //! \return ComponentSetter 25 | template ::Type> 26 | ComponentSetter& set(U&& value) { 27 | mut() = GAIA_FWD(value); 28 | return *this; 29 | } 30 | 31 | //! Returns a mutable reference to component. 32 | //! \tparam T Component or pair 33 | //! \return Reference to data for AoS, or mutable accessor for SoA types 34 | template 35 | decltype(auto) mut(Entity type) { 36 | return const_cast(m_pChunk)->template set(m_row, type); 37 | } 38 | 39 | //! Sets the value of the component @a type. 40 | //! \tparam T Component or pair 41 | //! \param type Entity associated with the type 42 | //! \param value Value to set for the component 43 | //! \return ComponentSetter 44 | template 45 | ComponentSetter& set(Entity type, T&& value) { 46 | mut(type) = GAIA_FWD(value); 47 | return *this; 48 | } 49 | 50 | //! Returns a mutable reference to component without triggering a world version update. 51 | //! \tparam T Component or pair 52 | //! \return Reference to data for AoS, or mutable accessor for SoA types 53 | template 54 | decltype(auto) smut() { 55 | return const_cast(m_pChunk)->template sset(m_row); 56 | } 57 | 58 | //! Sets the value of the component without triggering a world version update. 59 | //! \tparam T Component or pair 60 | //! \param value Value to set for the component 61 | //! \return ComponentSetter 62 | template ::Type> 63 | ComponentSetter& sset(U&& value) { 64 | smut() = GAIA_FWD(value); 65 | return *this; 66 | } 67 | 68 | //! Returns a mutable reference to component without triggering a world version update. 69 | //! \tparam T Component or pair 70 | //! \param type Entity associated with the type 71 | //! \return Reference to data for AoS, or mutable accessor for SoA types 72 | template 73 | decltype(auto) smut(Entity type) { 74 | return const_cast(m_pChunk)->template sset(type); 75 | } 76 | 77 | //! Sets the value of the component without triggering a world version update. 78 | //! \tparam T Component or pair 79 | //! \param type Entity associated with the type 80 | //! \param value Value to set for the component 81 | //! \return ComponentSetter 82 | template 83 | ComponentSetter& sset(Entity type, T&& value) { 84 | smut(type) = GAIA_FWD(value); 85 | return *this; 86 | } 87 | }; 88 | } // namespace ecs 89 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/query_mask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include "gaia/ecs/component.h" 5 | #include "gaia/ecs/id.h" 6 | 7 | namespace gaia { 8 | namespace ecs { 9 | struct Entity; 10 | 11 | #if GAIA_USE_PARTITIONED_BLOOM_FILTER 12 | static constexpr uint64_t s_ct_queryMask_primes[4] = { 13 | 11400714819323198485ull, // golden ratio 14 | 14029467366897019727ull, // 15 | 1609587929392839161ull, // 16 | 9650029242287828579ull // 17 | }; 18 | 19 | struct QueryMask { 20 | uint64_t value[4]; 21 | 22 | bool operator==(const QueryMask& other) const { 23 | return ((value[0] ^ other.value[0]) | // 24 | (value[1] ^ other.value[1]) | // 25 | (value[2] ^ other.value[2]) | // 26 | (value[3] ^ other.value[3])) == 0; 27 | } 28 | bool operator!=(const QueryMask& other) const { 29 | return !(*this == other); 30 | } 31 | }; 32 | 33 | //! Hash an entity id into one bit per 64-bit block 34 | GAIA_NODISCARD inline QueryMask hash_entity_id(Entity entity) { 35 | QueryMask mask{}; 36 | const uint64_t id = entity.id(); 37 | 38 | // Pick 1 bit in each 16-bit block. 39 | // We are shifting right by 60 bits, leaving only the top 4 bits of the 64-bit product. This effectively 40 | // gives us a random-ish value in the range 0–15, which fits one bit per 16-bit block (since 2^4 = 16). 41 | const auto bit0 = (id * s_ct_queryMask_primes[0]) >> (64 - 4); 42 | const auto bit1 = (id * s_ct_queryMask_primes[1]) >> (64 - 4); 43 | const auto bit2 = (id * s_ct_queryMask_primes[2]) >> (64 - 4); 44 | const auto bit3 = (id * s_ct_queryMask_primes[3]) >> (64 - 4); 45 | 46 | mask.value[0] = 1ull << bit0; 47 | mask.value[1] = 1ull << bit1; 48 | mask.value[2] = 1ull << bit2; 49 | mask.value[3] = 1ull << bit3; 50 | 51 | return mask; 52 | } 53 | 54 | //! Builds a partitioned bloom mask from a list of entity IDs 55 | GAIA_NODISCARD inline QueryMask build_entity_mask(EntitySpan entities) { 56 | QueryMask result{}; 57 | for (auto entity: entities) { 58 | QueryMask hash = hash_entity_id(entity); 59 | result.value[0] |= hash.value[0]; 60 | result.value[1] |= hash.value[1]; 61 | result.value[2] |= hash.value[2]; 62 | result.value[3] |= hash.value[3]; 63 | } 64 | return result; 65 | } 66 | 67 | //! Checks is there is a match between two masks 68 | GAIA_NODISCARD inline bool match_entity_mask(const QueryMask& m1, const QueryMask& m2) { 69 | const uint64_t r0 = m1.value[0] & m2.value[0]; 70 | const uint64_t r1 = m1.value[1] & m2.value[1]; 71 | const uint64_t r2 = m1.value[2] & m2.value[2]; 72 | const uint64_t r3 = m1.value[3] & m2.value[3]; 73 | return bool(int(r0 != 0) & int(r1 != 0) & int(r2 != 0) & int(r3 != 0)); 74 | } 75 | #else 76 | using QueryMask = uint64_t; 77 | 78 | //! Hash an entity id into a mask 79 | GAIA_NODISCARD inline QueryMask hash_entity_id(Entity entity) { 80 | return (entity.id() * 11400714819323198485ull) >> (64 - 6); 81 | } 82 | 83 | //! Builds a bloom mask from a list of entity IDs 84 | GAIA_NODISCARD inline QueryMask build_entity_mask(EntitySpan entities) { 85 | QueryMask mask = 0; 86 | for (auto entity: entities) 87 | mask |= (1ull << hash_entity_id(entity)); 88 | 89 | return mask; 90 | } 91 | 92 | //! Checks is there is a match between two masks 93 | GAIA_NODISCARD inline bool match_entity_mask(const QueryMask& m1, const QueryMask& m2) { 94 | return (m1 & m2) != 0; 95 | } 96 | #endif 97 | } // namespace ecs 98 | } // namespace gaia 99 | -------------------------------------------------------------------------------- /include/gaia/mem/mem_sani.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef GAIA_USE_MEM_SANI 4 | #if defined(__has_feature) 5 | #if __has_feature(address_sanitizer) 6 | #define GAIA_HAS_SANI_FEATURE 1 7 | #else 8 | #define GAIA_HAS_SANI_FEATURE 0 9 | #endif 10 | #if (GAIA_HAS_SANI_FEATURE && GAIA_USE_SANITIZER) || defined(__SANITIZE_ADDRESS__) 11 | #define GAIA_USE_MEM_SANI 1 12 | #else 13 | #define GAIA_USE_MEM_SANI 0 14 | #endif 15 | #else 16 | #if GAIA_USE_SANITIZER || defined(__SANITIZE_ADDRESS__) 17 | #define GAIA_USE_MEM_SANI 1 18 | #else 19 | #define GAIA_USE_MEM_SANI 0 20 | #endif 21 | #endif 22 | #endif 23 | 24 | #if GAIA_USE_MEM_SANI 25 | #include 26 | 27 | // Poison a new contiguous block of memory 28 | inline void GAIA_MEM_SANI_ADD_BLOCK(size_t type_size, void* ptr, size_t cap, size_t size) { 29 | if (ptr == nullptr) 30 | return; 31 | 32 | __sanitizer_annotate_contiguous_container( 33 | ptr, /**/ 34 | (unsigned char*)(ptr) + ((cap)*type_size), /**/ 35 | ptr, /**/ 36 | (unsigned char*)(ptr) + ((size)*type_size)); 37 | } 38 | 39 | // Unpoison an existing contiguous block of buffer 40 | inline void GAIA_MEM_SANI_DEL_BLOCK(size_t type_size, void* ptr, size_t cap, size_t size) { 41 | if (ptr == nullptr) 42 | return; 43 | 44 | __sanitizer_annotate_contiguous_container( 45 | ptr, /**/ 46 | (unsigned char*)(ptr) + ((cap)*type_size), /**/ 47 | (unsigned char*)(ptr) + ((size)*type_size), /**/ 48 | ptr); 49 | } 50 | 51 | // Unpoison memory for N new elements, use before adding the elements 52 | inline void GAIA_MEM_SANI_PUSH_N(size_t type_size, void* ptr, size_t cap, size_t size, size_t diff) { 53 | if (ptr == nullptr) 54 | return; 55 | 56 | __sanitizer_annotate_contiguous_container( 57 | ptr, /**/ 58 | (unsigned char*)(ptr) + ((cap)*type_size), /**/ 59 | (unsigned char*)(ptr) + ((size)*type_size), /**/ 60 | (unsigned char*)(ptr) + (((size) + (diff)) * type_size)); 61 | } 62 | // Poison memory for last N elements, use after removing the elements 63 | inline void GAIA_MEM_SANI_POP_N(size_t type_size, void* ptr, size_t cap, size_t size, size_t diff) { 64 | if (ptr == nullptr) 65 | return; 66 | 67 | __sanitizer_annotate_contiguous_container( 68 | ptr, /**/ 69 | (unsigned char*)(ptr) + ((cap)*type_size), /**/ 70 | (unsigned char*)(ptr) + ((size)*type_size), /**/ 71 | (unsigned char*)(ptr) + (((size) - (diff)) * type_size)); 72 | } 73 | 74 | // Unpoison memory for a new element, use before adding it 75 | #define GAIA_MEM_SANI_PUSH(type_size, ptr, cap, size) GAIA_MEM_SANI_PUSH_N(type_size, ptr, cap, size, 1) 76 | // Poison memory for the last elements, use after removing it 77 | #define GAIA_MEM_SANI_POP(type_size, ptr, cap, size) GAIA_MEM_SANI_POP_N(type_size, ptr, cap, size, 1) 78 | 79 | #else 80 | #define GAIA_MEM_SANI_ADD_BLOCK(type_size, ptr, cap, size) ((void)(type_size), (void)(ptr), (void)(cap), (void)(size)) 81 | #define GAIA_MEM_SANI_DEL_BLOCK(type_size, ptr, cap, size) ((void)(type_size), (void)(ptr), (void)(cap), (void)(size)) 82 | #define GAIA_MEM_SANI_PUSH_N(type_size, ptr, cap, size, diff) \ 83 | ((void)(type_size), (void)(ptr), (void)(cap), (void)(size), (void)(diff)) 84 | #define GAIA_MEM_SANI_POP_N(type_size, ptr, cap, size, diff) \ 85 | ((void)(type_size), (void)(ptr), (void)(cap), (void)(size), (void)(diff)) 86 | #define GAIA_MEM_SANI_PUSH(type_size, ptr, cap, size) ((void)(type_size), (void)(ptr), (void)(cap), (void)(size)) 87 | #define GAIA_MEM_SANI_POP(type_size, ptr, cap, size) ((void)(type_size), (void)(ptr), (void)(cap), (void)(size)) 88 | #endif 89 | -------------------------------------------------------------------------------- /include/gaia/mt/semaphore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gaia/config/config.h" 4 | 5 | #if GAIA_PLATFORM_WINDOWS 6 | #include 7 | #elif GAIA_PLATFORM_APPLE 8 | #include 9 | #include 10 | #include 11 | #else 12 | #include 13 | #include 14 | #endif 15 | 16 | namespace gaia { 17 | namespace mt { 18 | class GAIA_API Semaphore final { 19 | #if GAIA_PLATFORM_WINDOWS 20 | void* m_handle; 21 | #elif GAIA_PLATFORM_APPLE 22 | dispatch_semaphore_t m_handle; 23 | #else 24 | sem_t m_handle; 25 | #endif 26 | 27 | Semaphore(Semaphore&&) = delete; 28 | Semaphore(const Semaphore&) = delete; 29 | Semaphore& operator=(Semaphore&&) = delete; 30 | Semaphore& operator=(const Semaphore&) = delete; 31 | 32 | //! Initializes the semaphore. 33 | //! \param count Initial count on semaphore. 34 | void init(int32_t count) { 35 | #if GAIA_PLATFORM_WINDOWS 36 | m_handle = (void*)::CreateSemaphoreExW(NULL, count, INT_MAX, NULL, 0, SEMAPHORE_ALL_ACCESS); 37 | GAIA_ASSERT(m_handle != NULL); 38 | #elif GAIA_PLATFORM_APPLE 39 | m_handle = dispatch_semaphore_create(count); 40 | GAIA_ASSERT(m_handle != nullptr); 41 | #else 42 | [[maybe_unused]] int ret = sem_init(&m_handle, 0, count); 43 | GAIA_ASSERT(ret == 0); 44 | #endif 45 | } 46 | 47 | //! Destroys the semaphore. 48 | void done() { 49 | #if GAIA_PLATFORM_WINDOWS 50 | if (m_handle != NULL) { 51 | ::CloseHandle((HANDLE)m_handle); 52 | m_handle = NULL; 53 | } 54 | #elif GAIA_PLATFORM_APPLE 55 | // NOTE: Dispatch objects are reference counted. 56 | // They are automatically released when no longer used. 57 | // -> dispatch_release(m_handle); 58 | #else 59 | [[maybe_unused]] int ret = sem_destroy(&m_handle); 60 | GAIA_ASSERT(ret == 0); 61 | #endif 62 | } 63 | 64 | public: 65 | explicit Semaphore(int32_t count = 0) { 66 | init(count); 67 | } 68 | 69 | ~Semaphore() { 70 | done(); 71 | } 72 | 73 | //! Increments semaphore count by the specified amount. 74 | void release(int32_t count) { 75 | GAIA_ASSERT(count > 0); 76 | 77 | #if GAIA_PLATFORM_WINDOWS 78 | [[maybe_unused]] LONG prev = 0; 79 | [[maybe_unused]] BOOL res = ::ReleaseSemaphore(m_handle, count, &prev); 80 | GAIA_ASSERT(res != 0); 81 | #elif GAIA_PLATFORM_APPLE 82 | do { 83 | dispatch_semaphore_signal(m_handle); 84 | } while ((--count) != 0); 85 | #else 86 | do { 87 | [[maybe_unused]] const auto ret = sem_post(&m_handle); 88 | GAIA_ASSERT(ret == 0); 89 | } while ((--count) != 0); 90 | #endif 91 | } 92 | 93 | //! Decrements semaphore count by 1. 94 | //! If the count is already 0, it waits indefinitely until semaphore count is incremented, 95 | //! then decrements and returns. Returns false when an error occurs, otherwise returns true. 96 | bool wait() { 97 | #if GAIA_PLATFORM_WINDOWS 98 | GAIA_ASSERT(m_handle != (void*)ERROR_INVALID_HANDLE); 99 | DWORD ret = ::WaitForSingleObject(m_handle, INFINITE); 100 | GAIA_ASSERT(ret == WAIT_OBJECT_0); 101 | return (ret == WAIT_OBJECT_0); 102 | #elif GAIA_PLATFORM_APPLE 103 | const auto res = dispatch_semaphore_wait(m_handle, DISPATCH_TIME_FOREVER); 104 | GAIA_ASSERT(res == 0); 105 | return (res == 0); 106 | #else 107 | int res; 108 | do { 109 | res = sem_wait(&m_handle); 110 | } while (res == -1 && errno == EINTR); // handle interrupts 111 | 112 | GAIA_ASSERT(res == 0); 113 | return (res == 0); 114 | #endif 115 | } 116 | }; 117 | } // namespace mt 118 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/meta/type_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include "gaia/core/hashing_policy.h" 5 | #include "gaia/core/span.h" 6 | 7 | namespace gaia { 8 | namespace meta { 9 | 10 | //! Provides statically generated unique identifier for a given group of types. 11 | template 12 | class type_group { 13 | inline static uint32_t s_identifier{}; 14 | 15 | public: 16 | template 17 | inline static const uint32_t id = s_identifier++; 18 | }; 19 | 20 | template <> 21 | class type_group; 22 | 23 | //---------------------------------------------------------------------- 24 | // Type meta data 25 | //---------------------------------------------------------------------- 26 | 27 | struct type_info final { 28 | private: 29 | constexpr static size_t find_first_of(const char* data, size_t len, char toFind, size_t startPos = 0) { 30 | for (size_t i = startPos; i < len; ++i) { 31 | if (data[i] == toFind) 32 | return i; 33 | } 34 | return size_t(-1); 35 | } 36 | 37 | constexpr static size_t find_last_of(const char* data, size_t len, char c, size_t startPos = size_t(-1)) { 38 | const auto minValue = startPos <= len - 1 ? startPos : len - 1; 39 | for (int64_t i = (int64_t)minValue; i >= 0; --i) { 40 | if (data[i] == c) 41 | return (size_t)i; 42 | } 43 | return size_t(-1); 44 | } 45 | 46 | public: 47 | template 48 | static uint32_t id() noexcept { 49 | return type_group::id; 50 | } 51 | 52 | template 53 | GAIA_NODISCARD static constexpr const char* full_name() noexcept { 54 | return GAIA_PRETTY_FUNCTION; 55 | } 56 | 57 | template 58 | GAIA_NODISCARD static constexpr auto name() noexcept { 59 | // MSVC: 60 | // const char* __cdecl ecs::ComponentInfo::name(void) 61 | // -> ecs::EnfEntity 62 | // Clang/Clang-cl/GCC: 63 | // const ecs::ComponentInfo::name() [T = ecs::EnfEntity] 64 | // -> ecs::EnfEntity 65 | 66 | // Note: 67 | // We don't want to use std::string_view here because it would only make it harder on compile-times. 68 | // In fact, even if we did, we need to be afraid of compiler issues. 69 | // Clang 8 and older wouldn't compile because their string_view::find_last_of doesn't work 70 | // in constexpr context. Tested with and without LIBCPP 71 | // https://stackoverflow.com/questions/56484834/constexpr-stdstring-viewfind-last-of-doesnt-work-on-clang-8-with-libstdc 72 | // As a workaround find_first_of and find_last_of were implemented 73 | 74 | size_t strLen = 0; 75 | while (GAIA_PRETTY_FUNCTION[strLen] != '\0') 76 | ++strLen; 77 | 78 | std::span name{GAIA_PRETTY_FUNCTION, strLen}; 79 | const auto prefixPos = find_first_of(name.data(), name.size(), GAIA_PRETTY_FUNCTION_PREFIX); 80 | const auto start = find_first_of(name.data(), name.size(), ' ', prefixPos + 1); 81 | const auto end = find_last_of(name.data(), name.size(), GAIA_PRETTY_FUNCTION_SUFFIX); 82 | return name.subspan(start + 1, end - start - 1); 83 | } 84 | 85 | template 86 | GAIA_NODISCARD static constexpr auto hash() noexcept { 87 | #if GAIA_COMPILER_MSVC && _MSC_VER <= 1916 88 | GAIA_MSVC_WARNING_PUSH() 89 | GAIA_MSVC_WARNING_DISABLE(4307) 90 | #endif 91 | 92 | auto n = name(); 93 | return core::calculate_hash64(n.data(), n.size()); 94 | 95 | #if GAIA_COMPILER_MSVC && _MSC_VER <= 1916 96 | GAIA_MSVC_WARNING_PUSH() 97 | #endif 98 | } 99 | }; 100 | 101 | } // namespace meta 102 | } // namespace gaia 103 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "inputs": [ 7 | { 8 | "id": "buildType", 9 | "description": "Build configuration to pick", 10 | "type": "pickString", 11 | "options": [ 12 | "Debug", 13 | "RelWithDebInfo" 14 | ], 15 | "default": "Debug" 16 | } 17 | ], 18 | "configurations": [ 19 | { 20 | "name": "(lldb) example - dll", 21 | "type": "lldb", 22 | "request": "launch", 23 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example_app/gaia_example_app", 24 | "args": [], 25 | "cwd": "${workspaceFolder}", 26 | }, 27 | { 28 | "name": "(lldb) example - example1", 29 | "type": "lldb", 30 | "request": "launch", 31 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example1/gaia_example1", 32 | "args": [], 33 | "cwd": "${workspaceFolder}", 34 | }, 35 | { 36 | "name": "(lldb) example - example2", 37 | "type": "lldb", 38 | "request": "launch", 39 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example2/gaia_example2", 40 | "args": [], 41 | "cwd": "${workspaceFolder}", 42 | }, 43 | { 44 | "name": "(lldb) example - example3", 45 | "type": "lldb", 46 | "request": "launch", 47 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example3/gaia_example3", 48 | "args": [], 49 | "cwd": "${workspaceFolder}", 50 | }, 51 | { 52 | "name": "(lldb) example - roguelike", 53 | "type": "lldb", 54 | "request": "launch", 55 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example_roguelike/gaia_example_roguelike", 56 | "args": [], 57 | "cwd": "${workspaceFolder}", 58 | }, 59 | { 60 | "name": "(lldb) unit test", 61 | "type": "lldb", 62 | "request": "launch", 63 | "program": "${workspaceFolder}/build/${input:buildType}/src/test/gaia_test", 64 | "args": [], 65 | "cwd": "${workspaceFolder}", 66 | }, 67 | { 68 | "name": "(lldb) perf - duel", 69 | "type": "lldb", 70 | "request": "launch", 71 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/duel/gaia_perf_duel", 72 | "args": [], 73 | "cwd": "${workspaceFolder}", 74 | }, 75 | { 76 | "name": "(lldb) perf - iteration", 77 | "type": "lldb", 78 | "request": "launch", 79 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/iter/gaia_perf_iter", 80 | "args": [], 81 | "cwd": "${workspaceFolder}", 82 | }, 83 | { 84 | "name": "(lldb) perf - entity", 85 | "type": "lldb", 86 | "request": "launch", 87 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/entity/gaia_perf_entity", 88 | "args": [], 89 | "cwd": "${workspaceFolder}", 90 | }, 91 | { 92 | "name": "(lldb) perf - mt", 93 | "type": "lldb", 94 | "request": "launch", 95 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/mt/gaia_perf_mt", 96 | "args": [], 97 | "cwd": "${workspaceFolder}", 98 | }, 99 | { 100 | "name": "(lldb) perf - app", 101 | "type": "lldb", 102 | "request": "launch", 103 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/app/gaia_perf_app", 104 | "args": [], 105 | "cwd": "${workspaceFolder}", 106 | } 107 | ] 108 | } -------------------------------------------------------------------------------- /include/gaia/mt/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | #include "gaia/config/profiler.h" 4 | 5 | #if GAIA_PLATFORM_WINDOWS 6 | #define GAIA_USE_MT_STD 1 7 | #endif 8 | 9 | #if GAIA_USE_MT_STD 10 | #include 11 | #include 12 | #else 13 | #include 14 | #endif 15 | 16 | namespace gaia { 17 | namespace mt { 18 | class GAIA_API Event final { 19 | #if GAIA_USE_MT_STD 20 | GAIA_PROF_MUTEX(std::mutex, m_mtx); 21 | std::condition_variable m_cv; 22 | bool m_set = false; 23 | #else 24 | pthread_cond_t m_hCondHandle; 25 | pthread_mutex_t m_hMutexHandle; 26 | bool m_set = false; 27 | #endif 28 | 29 | public: 30 | #if !GAIA_USE_MT_STD 31 | Event() { 32 | [[maybe_unused]] int ret = pthread_mutex_init(&m_hMutexHandle, nullptr); 33 | GAIA_ASSERT(ret == 0); 34 | if (ret == 0) { 35 | ret = pthread_cond_init(&m_hCondHandle, nullptr); 36 | GAIA_ASSERT(ret == 0); 37 | } 38 | } 39 | 40 | ~Event() { 41 | [[maybe_unused]] int ret = pthread_cond_destroy(&m_hCondHandle); 42 | GAIA_ASSERT(ret == 0); 43 | 44 | ret = pthread_mutex_destroy(&m_hMutexHandle); 45 | GAIA_ASSERT(ret == 0); 46 | } 47 | #endif 48 | 49 | void set() { 50 | #if GAIA_USE_MT_STD 51 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 52 | std::unique_lock lock(mtx); 53 | GAIA_PROF_LOCK_MARK(m_mtx); 54 | m_set = true; 55 | m_cv.notify_one(); 56 | #else 57 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 58 | GAIA_ASSERT(ret == 0); 59 | m_set = true; 60 | 61 | // Depending on the event type, we either trigger everyone waiting or just one 62 | ret = pthread_cond_signal(&m_hCondHandle); 63 | GAIA_ASSERT(ret == 0); 64 | 65 | ret = pthread_mutex_unlock(&m_hMutexHandle); 66 | GAIA_ASSERT(ret == 0); 67 | #endif 68 | } 69 | 70 | void reset() { 71 | #if GAIA_USE_MT_STD 72 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 73 | std::unique_lock lock(mtx); 74 | GAIA_PROF_LOCK_MARK(m_mtx); 75 | m_set = false; 76 | #else 77 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 78 | GAIA_ASSERT(ret == 0); 79 | m_set = false; 80 | ret = pthread_mutex_unlock(&m_hMutexHandle); 81 | GAIA_ASSERT(ret == 0); 82 | #endif 83 | } 84 | 85 | GAIA_NODISCARD bool is_set() { 86 | #if GAIA_USE_MT_STD 87 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 88 | std::unique_lock lock(mtx); 89 | GAIA_PROF_LOCK_MARK(m_mtx); 90 | return m_set; 91 | #else 92 | bool set{}; 93 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 94 | GAIA_ASSERT(ret == 0); 95 | set = m_set; 96 | ret = pthread_mutex_unlock(&m_hMutexHandle); 97 | GAIA_ASSERT(ret == 0); 98 | return set; 99 | #endif 100 | } 101 | 102 | void wait() { 103 | #if GAIA_USE_MT_STD 104 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 105 | std::unique_lock lock(mtx); 106 | GAIA_PROF_LOCK_MARK(m_mtx); 107 | m_cv.wait(lock, [&] { 108 | return m_set; 109 | }); 110 | #else 111 | [[maybe_unused]] int ret{}; 112 | auto wait = [&]() { 113 | if (!m_set) { 114 | do { 115 | ret = pthread_cond_wait(&m_hCondHandle, &m_hMutexHandle); 116 | } while (!ret && !m_set); 117 | 118 | GAIA_ASSERT(ret != EINVAL); 119 | if (!ret) 120 | m_set = false; 121 | } else { 122 | ret = 0; 123 | } 124 | 125 | return ret; 126 | }; 127 | 128 | ret = pthread_mutex_lock(&m_hMutexHandle); 129 | GAIA_ASSERT(ret == 0); 130 | 131 | int res = wait(); // true: signaled, false: timeout or error 132 | (void)res; 133 | 134 | ret = pthread_mutex_unlock(&m_hMutexHandle); 135 | GAIA_ASSERT(ret == 0); 136 | #endif 137 | } 138 | }; // namespace mt 139 | } // namespace mt 140 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/mt/jobhandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace gaia { 8 | namespace mt { 9 | using JobInternalType = uint32_t; 10 | using JobId = JobInternalType; 11 | using JobGenId = JobInternalType; 12 | 13 | struct GAIA_API JobHandle final { 14 | static constexpr JobInternalType IdBits = 20; 15 | static constexpr JobInternalType GenBits = 11; 16 | static constexpr JobInternalType PrioBits = 1; 17 | static constexpr JobInternalType AllBits = IdBits + GenBits + PrioBits; 18 | static constexpr JobInternalType IdMask = (uint32_t)(uint64_t(1) << IdBits) - 1; 19 | static constexpr JobInternalType GenMask = (uint32_t)(uint64_t(1) << GenBits) - 1; 20 | static constexpr JobInternalType PrioMask = (uint32_t)(uint64_t(1) << PrioBits) - 1; 21 | 22 | using JobSizeType = std::conditional_t<(AllBits > 32), uint64_t, uint32_t>; 23 | 24 | static_assert(AllBits <= 64, "Job IdBits and GenBits must fit inside 64 bits"); 25 | static_assert(IdBits <= 31, "Job IdBits must be at most 31 bits long"); 26 | static_assert(GenBits > 10, "Job GenBits must be at least 10 bits long"); 27 | 28 | private: 29 | struct JobData { 30 | //! Index in entity array 31 | JobInternalType id : IdBits; 32 | //! Generation index. Incremented every time an item is deleted 33 | JobInternalType gen : GenBits; 34 | //! Job priority. 1-priority, 0-background 35 | JobInternalType prio : PrioBits; 36 | }; 37 | 38 | union { 39 | JobData data; 40 | JobSizeType val; 41 | }; 42 | 43 | public: 44 | JobHandle() noexcept = default; 45 | JobHandle(JobId id, JobGenId gen, JobGenId prio) { 46 | data.id = id; 47 | data.gen = gen; 48 | data.prio = prio; 49 | } 50 | explicit JobHandle(uint32_t value) { 51 | val = value; 52 | } 53 | ~JobHandle() = default; 54 | 55 | JobHandle(JobHandle&&) noexcept = default; 56 | JobHandle(const JobHandle&) = default; 57 | JobHandle& operator=(JobHandle&&) noexcept = default; 58 | JobHandle& operator=(const JobHandle&) = default; 59 | 60 | GAIA_NODISCARD constexpr bool operator==(const JobHandle& other) const noexcept { 61 | return val == other.val; 62 | } 63 | GAIA_NODISCARD constexpr bool operator!=(const JobHandle& other) const noexcept { 64 | return val != other.val; 65 | } 66 | 67 | GAIA_NODISCARD auto id() const { 68 | return data.id; 69 | } 70 | GAIA_NODISCARD auto gen() const { 71 | return data.gen; 72 | } 73 | GAIA_NODISCARD auto prio() const { 74 | return data.prio; 75 | } 76 | GAIA_NODISCARD auto value() const { 77 | return val; 78 | } 79 | }; 80 | 81 | struct JobNull_t { 82 | GAIA_NODISCARD operator JobHandle() const noexcept { 83 | return JobHandle(JobHandle::IdMask, JobHandle::GenMask, JobHandle::PrioMask); 84 | } 85 | 86 | GAIA_NODISCARD constexpr bool operator==([[maybe_unused]] const JobNull_t& null) const noexcept { 87 | return true; 88 | } 89 | GAIA_NODISCARD constexpr bool operator!=([[maybe_unused]] const JobNull_t& null) const noexcept { 90 | return false; 91 | } 92 | }; 93 | 94 | GAIA_NODISCARD inline bool operator==(const JobNull_t& null, const JobHandle& entity) noexcept { 95 | return static_cast(null).id() == entity.id(); 96 | } 97 | 98 | GAIA_NODISCARD inline bool operator!=(const JobNull_t& null, const JobHandle& entity) noexcept { 99 | return static_cast(null).id() != entity.id(); 100 | } 101 | 102 | GAIA_NODISCARD inline bool operator==(const JobHandle& entity, const JobNull_t& null) noexcept { 103 | return null == entity; 104 | } 105 | 106 | GAIA_NODISCARD inline bool operator!=(const JobHandle& entity, const JobNull_t& null) noexcept { 107 | return null != entity; 108 | } 109 | 110 | inline constexpr JobNull_t JobNull{}; 111 | } // namespace mt 112 | } // namespace gaia 113 | -------------------------------------------------------------------------------- /include/gaia/ecs/api.inl: -------------------------------------------------------------------------------- 1 | #include "gaia/config/config.h" 2 | 3 | namespace gaia { 4 | namespace ecs { 5 | // Component API 6 | 7 | GAIA_NODISCARD inline const ComponentCache& comp_cache(const World& world) { 8 | return world.comp_cache(); 9 | } 10 | 11 | GAIA_NODISCARD inline ComponentCache& comp_cache_mut(World& world) { 12 | return world.comp_cache_mut(); 13 | } 14 | 15 | template 16 | GAIA_NODISCARD inline const ComponentCacheItem& comp_cache_add(World& world) { 17 | return world.add(); 18 | } 19 | 20 | // Entity API 21 | 22 | GAIA_NODISCARD inline const EntityContainer& fetch(const World& world, Entity entity) { 23 | return world.fetch(entity); 24 | } 25 | 26 | GAIA_NODISCARD inline EntityContainer& fetch_mut(World& world, Entity entity) { 27 | return world.fetch(entity); 28 | } 29 | 30 | inline void del(World& world, Entity entity) { 31 | world.del(entity); 32 | } 33 | 34 | GAIA_NODISCARD inline Entity entity_from_id(const World& world, EntityId id) { 35 | return world.get(id); 36 | } 37 | 38 | GAIA_NODISCARD inline bool valid(const World& world, Entity entity) { 39 | return world.valid(entity); 40 | } 41 | 42 | GAIA_NODISCARD inline bool is(const World& world, Entity entity, Entity baseEntity) { 43 | return world.is(entity, baseEntity); 44 | } 45 | 46 | GAIA_NODISCARD inline bool is_base(const World& world, Entity entity) { 47 | return world.is_base(entity); 48 | } 49 | 50 | GAIA_NODISCARD inline Archetype* archetype_from_entity(const World& world, Entity entity) { 51 | const auto& ec = world.fetch(entity); 52 | if (World::is_req_del(ec)) 53 | return nullptr; 54 | 55 | return ec.pArchetype; 56 | } 57 | 58 | GAIA_NODISCARD inline const char* entity_name(const World& world, Entity entity) { 59 | return world.name(entity); 60 | } 61 | 62 | GAIA_NODISCARD inline const char* entity_name(const World& world, EntityId entityId) { 63 | return world.name(entityId); 64 | } 65 | 66 | // Traversal API 67 | 68 | template 69 | inline void as_relations_trav(const World& world, Entity target, Func func) { 70 | world.as_relations_trav(target, func); 71 | } 72 | 73 | template 74 | inline bool as_relations_trav_if(const World& world, Entity target, Func func) { 75 | return world.as_relations_trav_if(target, func); 76 | } 77 | 78 | template 79 | inline void as_targets_trav(const World& world, Entity relation, Func func) { 80 | world.as_targets_trav(relation, func); 81 | } 82 | 83 | template 84 | inline bool as_targets_trav_if(const World& world, Entity relation, Func func) { 85 | return world.as_targets_trav_if(relation, func); 86 | } 87 | 88 | // Query API 89 | 90 | GAIA_NODISCARD inline QuerySerBuffer& query_buffer(World& world, QueryId& serId) { 91 | return world.query_buffer(serId); 92 | } 93 | 94 | inline void query_buffer_reset(World& world, QueryId& serId) { 95 | world.query_buffer_reset(serId); 96 | } 97 | 98 | GAIA_NODISCARD inline Entity expr_to_entity(const World& world, va_list& args, std::span exprRaw) { 99 | return world.expr_to_entity(args, exprRaw); 100 | } 101 | 102 | // Locking API 103 | 104 | inline void lock(World& world) { 105 | world.lock(); 106 | } 107 | inline void unlock(World& world) { 108 | world.unlock(); 109 | } 110 | GAIA_NODISCARD inline bool locked(const World& world) { 111 | return world.locked(); 112 | } 113 | 114 | // CommandBuffer API 115 | 116 | GAIA_NODISCARD inline CommandBufferST& cmd_buffer_st_get(World& world) { 117 | return world.cmd_buffer_st(); 118 | } 119 | 120 | GAIA_NODISCARD inline CommandBufferMT& cmd_buffer_mt_get(World& world) { 121 | return world.cmd_buffer_mt(); 122 | } 123 | 124 | inline void commit_cmd_buffer_st(World& world) { 125 | if (world.locked()) 126 | return; 127 | cmd_buffer_commit(world.cmd_buffer_st()); 128 | } 129 | 130 | inline void commit_cmd_buffer_mt(World& world) { 131 | if (world.locked()) 132 | return; 133 | cmd_buffer_commit(world.cmd_buffer_mt()); 134 | } 135 | } // namespace ecs 136 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/core/iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace gaia { 8 | namespace core { 9 | struct input_iterator_tag {}; 10 | struct output_iterator_tag {}; 11 | 12 | struct forward_iterator_tag: input_iterator_tag {}; 13 | struct reverse_iterator_tag: input_iterator_tag {}; 14 | struct bidirectional_iterator_tag: forward_iterator_tag {}; 15 | struct random_access_iterator_tag: bidirectional_iterator_tag {}; 16 | struct contiguous_iterator_tag: random_access_iterator_tag {}; 17 | 18 | namespace detail { 19 | template 20 | struct iterator_traits_base {}; // empty for non-iterators 21 | 22 | template 23 | struct iterator_traits_base< 24 | It, std::void_t< 25 | typename It::iterator_category, typename It::value_type, typename It::difference_type, 26 | typename It::pointer, typename It::reference>> { 27 | using iterator_category = typename It::iterator_category; 28 | using value_type = typename It::value_type; 29 | using difference_type = typename It::difference_type; 30 | using pointer = typename It::pointer; 31 | using reference = typename It::reference; 32 | }; 33 | 34 | template > 35 | struct iterator_traits_pointer_base { 36 | using iterator_category = random_access_iterator_tag; 37 | using value_type = std::remove_cv_t; 38 | using difference_type = std::ptrdiff_t; 39 | using pointer = T*; 40 | using reference = T&; 41 | }; 42 | 43 | //! Iterator traits for pointers to non-object 44 | template 45 | struct iterator_traits_pointer_base {}; 46 | 47 | //! Iterator traits for iterators 48 | template 49 | struct iterator_traits: iterator_traits_base {}; 50 | 51 | // Iterator traits for pointers 52 | template 53 | struct iterator_traits: iterator_traits_pointer_base {}; 54 | 55 | template 56 | using iterator_cat_t = typename iterator_traits::iterator_category; 57 | } // namespace detail 58 | 59 | template 60 | [[maybe_unused]] constexpr bool is_iterator_v = false; 61 | 62 | template 63 | [[maybe_unused]] constexpr bool is_iterator_v>> = true; 64 | 65 | template 66 | struct is_iterator: std::bool_constant> {}; 67 | 68 | template 69 | [[maybe_unused]] constexpr bool is_input_iter_v = 70 | std::is_convertible_v, input_iterator_tag>; 71 | 72 | template 73 | [[maybe_unused]] constexpr bool is_fwd_iter_v = 74 | std::is_convertible_v, forward_iterator_tag>; 75 | 76 | template 77 | [[maybe_unused]] constexpr bool is_rev_iter_v = 78 | std::is_convertible_v, reverse_iterator_tag>; 79 | 80 | template 81 | [[maybe_unused]] constexpr bool is_bidi_iter_v = 82 | std::is_convertible_v, bidirectional_iterator_tag>; 83 | 84 | template 85 | [[maybe_unused]] constexpr bool is_random_iter_v = 86 | std::is_convertible_v, random_access_iterator_tag>; 87 | 88 | template 89 | using iterator_ref_t = typename detail::iterator_traits::reference; 90 | 91 | template 92 | using iterator_value_t = typename detail::iterator_traits::value_type; 93 | 94 | template 95 | using iterator_diff_t = typename detail::iterator_traits::difference_type; 96 | 97 | template 98 | using common_diff_t = std::common_type_t...>; 99 | 100 | template 101 | constexpr iterator_diff_t distance(It first, It last) { 102 | if constexpr (std::is_pointer_v || is_random_iter_v) 103 | return last - first; 104 | else { 105 | iterator_diff_t offset{}; 106 | while (first != last) { 107 | ++first; 108 | ++offset; 109 | } 110 | return offset; 111 | } 112 | } 113 | } // namespace core 114 | } // namespace gaia 115 | -------------------------------------------------------------------------------- /include/gaia/config/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config_core.h" 3 | #include "gaia/config/version.h" 4 | 5 | //------------------------------------------------------------------------------ 6 | // General settings. 7 | // You are free to modify these. 8 | //------------------------------------------------------------------------------ 9 | 10 | //! If enabled, GAIA_DEBUG is defined despite using a "Release" build configuration for example 11 | #if !defined(GAIA_FORCE_DEBUG) 12 | #define GAIA_FORCE_DEBUG 0 13 | #endif 14 | //! If enabled, no asserts are thrown even in debug builds 15 | #if !defined(GAIA_DISABLE_ASSERTS) 16 | #define GAIA_DISABLE_ASSERTS 0 17 | #endif 18 | 19 | //------------------------------------------------------------------------------ 20 | // Internal features. 21 | // You are free to modify these but you probably should not. 22 | // It is expected to only use them when something doesn't work as expected 23 | // or as some sort of workaround. 24 | //------------------------------------------------------------------------------ 25 | 26 | //! If enabled, custom allocator is used for allocating archetype chunks. 27 | #ifndef GAIA_ECS_CHUNK_ALLOCATOR 28 | #define GAIA_ECS_CHUNK_ALLOCATOR 1 29 | #endif 30 | 31 | //! Hashing algorithm. GAIA_ECS_HASH_FNV1A or GAIA_ECS_HASH_MURMUR2A 32 | #ifndef GAIA_ECS_HASH 33 | #define GAIA_ECS_HASH GAIA_ECS_HASH_MURMUR2A 34 | #endif 35 | 36 | //! If enabled, memory prefetching is used when querying chunks, possibly improving performance in edge-cases 37 | #ifndef GAIA_USE_PREFETCH 38 | #define GAIA_USE_PREFETCH 1 39 | #endif 40 | 41 | //! If enabled, systems as entities are enabled 42 | #ifndef GAIA_SYSTEMS_ENABLED 43 | #define GAIA_SYSTEMS_ENABLED 1 44 | #endif 45 | 46 | //! If enabled, entities are stored in paged-storage. This way, the cost of adding any number of entities 47 | //! is always the same. Blocks of fixed size and stable memory address are allocated for entity records. 48 | #ifndef GAIA_USE_PAGED_ENTITY_CONTAINER 49 | #define GAIA_USE_PAGED_ENTITY_CONTAINER 1 50 | #endif 51 | 52 | //! If enabled, hooks are enabled for components. Any time a new component is added to, or removed from 53 | //! an entity, they can be triggered. Set hooks for when component value is changed are possible, too. 54 | #ifndef GAIA_ENABLE_HOOKS 55 | #define GAIA_ENABLE_HOOKS 1 56 | #endif 57 | #ifndef GAIA_ENABLE_ADD_DEL_HOOKS 58 | #define GAIA_ENABLE_ADD_DEL_HOOKS (GAIA_ENABLE_HOOKS && 1) 59 | #else 60 | // If GAIA_ENABLE_ADD_DEL_HOOKS is defined and GAIA_ENABLE_HOOKS is not, unset it 61 | #ifndef GAIA_ENABLE_HOOKS 62 | #undef GAIA_ENABLE_ADD_DEL_HOOKS 63 | #define GAIA_ENABLE_ADD_DEL_HOOKS 0 64 | #endif 65 | #endif 66 | #ifndef GAIA_ENABLE_SET_HOOKS 67 | #define GAIA_ENABLE_SET_HOOKS (GAIA_ENABLE_HOOKS && 1) 68 | #else 69 | // If GAIA_ENABLE_SET_HOOKS is defined and GAIA_ENABLE_HOOKS is not, unset it 70 | #ifndef GAIA_ENABLE_HOOKS 71 | #undef GAIA_ENABLE_SET_HOOKS 72 | #define GAIA_ENABLE_SET_HOOKS 0 73 | #endif 74 | #endif 75 | 76 | //! If enabled, reference counting of entities is enabled. This gives you access to ecs::SafeEntity 77 | //! and similar features. 78 | #ifndef GAIA_USE_SAFE_ENTITY 79 | #define GAIA_USE_SAFE_ENTITY 1 80 | #endif 81 | 82 | //! If enabled, reference counting of entities is enabled. This gives you access to ecs::SafeEntity 83 | //! and similar features. 84 | #ifndef GAIA_USE_WEAK_ENTITY 85 | #define GAIA_USE_WEAK_ENTITY 1 86 | #endif 87 | 88 | //! If enabled, API supporting variadic template arguments is made available. 89 | //! More comfortable to use, but compilation times may suffer. 90 | #ifndef GAIA_USE_VARIADIC_API 91 | #define GAIA_USE_VARIADIC_API 0 92 | #endif 93 | 94 | //! If enabled, a bloom filter is used for speed up matching of archetypes in queries. 95 | //! If disabled, no filter is applied. 96 | //! Possible values: 97 | //! -1 - No filter 98 | //! 0 - Bloom filter (calculates a 8 byte hash for each archetype) 99 | //! 1 - Partitioned bloom filter (calculates 4x8 byte hash for each archetype) 100 | //! Partitioned bloom filter is slightly more computationaly expensive but gives less false postives. 101 | //! Therefore, it will be more useful when there is a lot of archetypes with very different components. 102 | #ifndef GAIA_USE_PARTITIONED_BLOOM_FILTER 103 | #define GAIA_USE_PARTITIONED_BLOOM_FILTER 1 104 | #endif 105 | 106 | //------------------------------------------------------------------------------ 107 | 108 | #include "gaia/config/config_core_end.h" 109 | -------------------------------------------------------------------------------- /vm/build_clang_cachegrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH_BASE="build-clang" 4 | 5 | while getopts ":c" flag; do 6 | case "${flag}" in 7 | c) # remove the build directory 8 | rm -rf ${PATH_BASE};; 9 | \?) # invalid option 10 | echo "Error: Invalid option" 11 | exit;; 12 | esac 13 | done 14 | 15 | mkdir ${PATH_BASE} -p 16 | 17 | #################################################################### 18 | # Compiler 19 | #################################################################### 20 | 21 | export CC="/usr/bin/clang" 22 | export CXX="/usr/bin/clang++" 23 | export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) 24 | 25 | #################################################################### 26 | # Build the project 27 | #################################################################### 28 | 29 | BUILD_SETTINGS_COMMON_BASE="-DGAIA_BUILD_UNITTEST=OFF -DGAIA_BUILD_BENCHMARK=ON -DGAIA_BUILD_EXAMPLES=OFF -DGAIA_GENERATE_CC=OFF -DGAIA_MAKE_SINGLE_HEADER=OFF -DGAIA_PROFILER_BUILD=OFF -DGAIA_GENERATE_DOCS=OFF" 30 | BUILD_SETTINGS_COMMON="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_PROFILER_CPU=OFF -DGAIA_PROFILER_MEM=OFF" 31 | PATH_RELEASE="./${PATH_BASE}/release-cachegrind" 32 | 33 | # Release mode 34 | cmake -E make_directory ${PATH_RELEASE} 35 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ${BUILD_SETTINGS_COMMON} -DGAIA_DEBUG=0 -S .. -B ${PATH_RELEASE} 36 | if ! cmake --build ${PATH_RELEASE} --config RelWithDebInfo; then 37 | echo "${PATH_DEBUG} build failed" 38 | exit 1 39 | fi 40 | 41 | #################################################################### 42 | # Run cachegrind 43 | #################################################################### 44 | 45 | OUTPUT_BASE="src/perf/duel/gaia_perf_duel" 46 | OUTPUT_ARGS_DOD="-p -dod" 47 | OUTPUT_ARGS_ECS="-p" 48 | 49 | VALGRIND_ARGS="--tool=cachegrind" 50 | 51 | # Debug mode 52 | chmod +x ${PATH_RELEASE}/${OUTPUT_BASE} 53 | 54 | # We need to adjust how cachegrind is called based on what CPU we have. 55 | CURRENT_PLATFORM=$(uname -p) 56 | if [[ "$CURRENT_PLATFORM" = "i386|i686|x86_64" ]]; then 57 | # Most Intel a AMD CPUs should work just fine using a generic cachegrind call 58 | VALGRIND_ARGS_CUSTOM="" 59 | else 60 | # If we are not an x86 CPU we will assume an ARM. Namely Apple M1. 61 | # Docker at least up to version 4.17 is somewhat broken for ARM CPUs and does not propagate /proc/cpuinfo to the virtual machine. 62 | # Therefore, there is no easy way for us to tell what CPU is used. Obviously, we could use a 3rd party program or write our own. 63 | # However, virtually noone besides the maintainers is going to use this tool so we take the incorrect but good-enough-for-now way 64 | # and will assume an Apple M1. 65 | 66 | # M1 chips are not detected properly so we have to force cache sizes. 67 | # M1 has both performance and efficiency cores which differ in their setup: 68 | # performance cores: I1=192kiB 8-way, D1=131kiB 8-way, L2=12MiB 16-way 69 | # efficiency cores : I1=128kiB 8-way, D1=64kiB 8-way, L2=4MiB 16-way 70 | # Unfortunatelly, Cachegrind thinks the cache can only be a power of 2 in size. Therefore, performance cores can't be measured 71 | # properly and we have to simulate at least the efficiency cores. 72 | 73 | # M1 performance core (won't run because L1 cache is not a power of 2): 74 | # VALGRIND_ARGS_CUSTOM="--I1=196608,8,128 --D1=131072,8,128 --L2=12582912,16,128 --cache-sim=yes" 75 | # M1 efficiency core: 76 | VALGRIND_ARGS_CUSTOM="--I1=131072,8,128 --D1=65536,8,128 --L2=4194304,16,128 --cache-sim=yes" 77 | fi 78 | 79 | echo "Cachegrind - measuring DOD performance" 80 | valgrind ${VALGRIND_ARGS} ${VALGRIND_ARGS_CUSTOM} --cachegrind-out-file=cachegrind.out.dod --branch-sim=yes "${PATH_RELEASE}/${OUTPUT_BASE}" ${OUTPUT_ARGS_DOD} 81 | cg_annotate --show=Dr,D1mr,DLmr --sort=Dr,D1mr,DLmr cachegrind.out.dod > cachegrind.r.dod # cache reads 82 | cg_annotate --show=Dw,D1mw,DLmw --sort=Dw,D1mw,DLmw cachegrind.out.dod > cachegrind.w.dod # cache writes 83 | cg_annotate --show=Bc,Bcm,Bi,Bim --sort=Bc,Bcm,Bi,Bim cachegrind.out.dod > cachegrind.b.dod # branch hits 84 | 85 | echo "Cachegrind - measuring ECS performance" 86 | valgrind ${VALGRIND_ARGS} ${VALGRIND_ARGS_CUSTOM} --cachegrind-out-file=cachegrind.out.ecs --branch-sim=yes "${PATH_RELEASE}/${OUTPUT_BASE}" ${OUTPUT_ARGS_ECS} 87 | cg_annotate --show=Dr,D1mr,DLmr --sort=Dr,D1mr,DLmr cachegrind.out.ecs > cachegrind.r.ecs 88 | cg_annotate --show=Dw,D1mw,DLmw --sort=Dw,D1mw,DLmw cachegrind.out.ecs > cachegrind.w.ecs 89 | cg_annotate --show=Bc,Bcm,Bi,Bim --sort=Bc,Bcm,Bi,Bim cachegrind.out.ecs > cachegrind.b.ecs 90 | -------------------------------------------------------------------------------- /include/gaia/mt/futex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | #include "gaia/config/profiler.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "gaia/core/utility.h" 9 | #include "gaia/mt/event.h" 10 | 11 | namespace gaia { 12 | namespace mt { 13 | namespace detail { 14 | inline static constexpr uint32_t WaitMaskAll = 0x7FFFFFFF; 15 | inline static constexpr uint32_t WaitMaskAny = ~0u; 16 | 17 | struct FutexWaitNode { 18 | FutexWaitNode* pNext = nullptr; 19 | const std::atomic_uint32_t* pFutexValue = nullptr; 20 | uint32_t waitMask = WaitMaskAny; 21 | Event evt; 22 | }; 23 | 24 | struct FutexBucket { 25 | GAIA_PROF_MUTEX(std::mutex, mtx); 26 | FutexWaitNode* pFirst = nullptr; 27 | 28 | // Since there shouldn't be that many threads waiting at any one time, this seems like a good 29 | // number of hash table buckets. Making it prime number for better spread. 30 | static constexpr uint32_t BUCKET_SIZE = 37; 31 | 32 | static FutexBucket& get(const std::atomic_uint32_t* pFutexValue) { 33 | static FutexBucket s_buckets[BUCKET_SIZE]; 34 | return s_buckets[(uintptr_t(pFutexValue) >> 2) % BUCKET_SIZE]; 35 | } 36 | }; 37 | 38 | inline thread_local FutexWaitNode t_WaitNode; 39 | 40 | } // namespace detail 41 | 42 | //! An implementation of a simple futex (fast userspace mutex). 43 | //! Only wait and wake are implemented. 44 | //! 45 | //! The main advantage of futex is performance. It avoids kernel involvement in uncontended cases. 46 | //! When there’s no contention, futexes allow threads to lock and unlock in userspace without entering 47 | //! the kernel, making operations significantly faster and reducing context-switch overhead. 48 | //! Only when there is contention does a futex use the kernel to put threads to sleep and wake them up, 49 | //! resulting in a hybrid model that is more efficient than mutexes, which always require kernel calls. 50 | //! 51 | //! TODO: Consider using WaitOnAddress for Windows, futex call for Linux etc. 52 | //! The current solution is platform-agnostic but platform-specific solutions might be more performant. 53 | struct Futex { 54 | enum class Result { 55 | //! Futex value didn't match the expected one 56 | Change, 57 | //! Futex woken up as a result of wake() 58 | WakeUp 59 | }; 60 | 61 | //! Suspends the caller on the futex while its value remains @a expected. 62 | //! \param pFutexValue Target futex 63 | //! \param expected Expected futex value 64 | //! \param waitMask Mask of waiters to wait for 65 | static Result wait(const std::atomic_uint32_t* pFutexValue, uint32_t expected, uint32_t waitMask) { 66 | GAIA_PROF_SCOPE(futex::wait); 67 | 68 | GAIA_ASSERT(waitMask != 0); 69 | 70 | auto& bucket = detail::FutexBucket::get(pFutexValue); 71 | auto& node = detail::t_WaitNode; 72 | node.pFutexValue = pFutexValue; 73 | node.waitMask = waitMask; 74 | 75 | { 76 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx); 77 | core::lock_scope lock(mtx); 78 | GAIA_PROF_LOCK_MARK(bucket.mtx); 79 | 80 | const uint32_t futexValue = pFutexValue->load(std::memory_order_relaxed); 81 | if (futexValue != expected) 82 | return Result::Change; 83 | 84 | node.pNext = bucket.pFirst; 85 | bucket.pFirst = &node; 86 | } 87 | 88 | node.evt.wait(); 89 | return Result::WakeUp; 90 | } 91 | 92 | //! Wakes up to @a wakeCount waiters whose @a waitMask matches @a wakeMask. 93 | //! \param pFutexValue Target futex 94 | //! \param wakeCount How many waiters are supposed to make up 95 | //! \param wakeMask Mask of callers to wake 96 | static uint32_t 97 | wake(const std::atomic_uint32_t* pFutexValue, uint32_t wakeCount, uint32_t wakeMask = detail::WaitMaskAny) { 98 | GAIA_PROF_SCOPE(futex::wake); 99 | 100 | GAIA_ASSERT(wakeMask != 0); 101 | 102 | auto& bucket = detail::FutexBucket::get(pFutexValue); 103 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx); 104 | core::lock_scope lock(mtx); 105 | GAIA_PROF_LOCK_MARK(bucket.mtx); 106 | 107 | uint32_t numAwoken = 0; 108 | auto** ppNode = &bucket.pFirst; 109 | for (auto* pNode = *ppNode; numAwoken < wakeCount && pNode != nullptr; pNode = *ppNode) { 110 | if (pNode->pFutexValue == pFutexValue && (pNode->waitMask & wakeMask) != 0) { 111 | ++numAwoken; 112 | 113 | // Unlink the node 114 | *ppNode = pNode->pNext; 115 | pNode->pNext = nullptr; 116 | 117 | pNode->evt.set(); 118 | } else { 119 | ppNode = &pNode->pNext; 120 | } 121 | } 122 | 123 | return numAwoken; 124 | } 125 | }; 126 | } // namespace mt 127 | } // namespace gaia -------------------------------------------------------------------------------- /gaia.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "editor.tabSize": 2, 9 | "editor.fontSize": 12, 10 | "files.associations": { 11 | "type_traits": "cpp", 12 | "span": "cpp", 13 | "array": "cpp", 14 | "iterator": "cpp", 15 | "string": "cpp", 16 | "string_view": "cpp", 17 | "vector": "cpp", 18 | "__bit_reference": "cpp", 19 | "__config": "cpp", 20 | "__debug": "cpp", 21 | "__errc": "cpp", 22 | "__functional_base": "cpp", 23 | "__hash_table": "cpp", 24 | "__locale": "cpp", 25 | "__mutex_base": "cpp", 26 | "__node_handle": "cpp", 27 | "__nullptr": "cpp", 28 | "__split_buffer": "cpp", 29 | "__string": "cpp", 30 | "__threading_support": "cpp", 31 | "__tree": "cpp", 32 | "__tuple": "cpp", 33 | "algorithm": "cpp", 34 | "atomic": "cpp", 35 | "bit": "cpp", 36 | "bitset": "cpp", 37 | "cctype": "cpp", 38 | "chrono": "cpp", 39 | "cmath": "cpp", 40 | "complex": "cpp", 41 | "csignal": "cpp", 42 | "cstdarg": "cpp", 43 | "cstddef": "cpp", 44 | "cstdint": "cpp", 45 | "cstdio": "cpp", 46 | "cstdlib": "cpp", 47 | "cstring": "cpp", 48 | "ctime": "cpp", 49 | "cwchar": "cpp", 50 | "cwctype": "cpp", 51 | "deque": "cpp", 52 | "exception": "cpp", 53 | "fstream": "cpp", 54 | "functional": "cpp", 55 | "future": "cpp", 56 | "initializer_list": "cpp", 57 | "iomanip": "cpp", 58 | "ios": "cpp", 59 | "iosfwd": "cpp", 60 | "iostream": "cpp", 61 | "istream": "cpp", 62 | "limits": "cpp", 63 | "list": "cpp", 64 | "locale": "cpp", 65 | "map": "cpp", 66 | "memory": "cpp", 67 | "mutex": "cpp", 68 | "new": "cpp", 69 | "numeric": "cpp", 70 | "optional": "cpp", 71 | "ostream": "cpp", 72 | "random": "cpp", 73 | "ratio": "cpp", 74 | "regex": "cpp", 75 | "set": "cpp", 76 | "sstream": "cpp", 77 | "stack": "cpp", 78 | "stdexcept": "cpp", 79 | "streambuf": "cpp", 80 | "strstream": "cpp", 81 | "system_error": "cpp", 82 | "thread": "cpp", 83 | "tuple": "cpp", 84 | "typeindex": "cpp", 85 | "typeinfo": "cpp", 86 | "unordered_map": "cpp", 87 | "utility": "cpp", 88 | "valarray": "cpp", 89 | "variant": "cpp", 90 | "memory_resource": "cpp", 91 | "filesystem": "cpp", 92 | "*.ipp": "cpp", 93 | "cassert": "cpp", 94 | "any": "cpp" 95 | }, 96 | "C_Cpp.clang_format_style": "file", 97 | "C_Cpp.clang_format_fallbackStyle": "LLVM", 98 | "C_Cpp.clang_format_sortIncludes": true, 99 | "[cpp]": { 100 | "editor.formatOnPaste": true, 101 | "editor.formatOnSave": true, 102 | }, 103 | "C_Cpp.default.cppStandard": "c++17", 104 | "C_Cpp.default.cStandard": "c17", 105 | "C_Cpp.autocompleteAddParentheses": true, 106 | "clangd.onConfigChanged": "restart", 107 | "clangd.arguments": [ 108 | "--compile-commands-dir=./ninja" 109 | ], 110 | "cmake.useProjectStatusView": false, 111 | "cmake.buildDirectory": "${workspaceFolder}/build/${buildType}", 112 | "vsicons.projectDetection.autoReload": true, 113 | "cmake.options.statusBarVisibility": "compact", 114 | "cSpell.words": [ 115 | "defrag", 116 | "defragment", 117 | "defragmentation", 118 | "defragmenting", 119 | "defragments", 120 | "DEVMODE", 121 | "futex", 122 | "futexes", 123 | "getaffinity", 124 | "mpmc", 125 | "nullptr", 126 | "PATHFINDING", 127 | "PICO", 128 | "picobench", 129 | "prefetchnta", 130 | "reimplementation", 131 | "ribegin", 132 | "riend", 133 | "Ringbuffer", 134 | "rvalues", 135 | "SANI", 136 | "setaffinity", 137 | "SIMD", 138 | "subviews", 139 | "uncontended", 140 | "unpoison", 141 | "vectorize", 142 | "xoshiro" 143 | ], 144 | "cSpell.ignoreWords": [ 145 | "ALLC", 146 | "CPPUNWIND", 147 | "FUNCSIG", 148 | "HRESULT", 149 | "LAMBDAINLINE", 150 | "LIBCPP", 151 | "LPCSTR", 152 | "PCWSTR", 153 | "STRFMT", 154 | "STRINGIZE", 155 | "TEMPENTITY", 156 | "Wshadow", 157 | "Wmissing", 158 | "alig", 159 | "arre", 160 | "astar", 161 | "clzll", 162 | "constexpr", 163 | "ctzll", 164 | "darray", 165 | "dbitset", 166 | "dpos", 167 | "ename", 168 | "ents", 169 | "fetchu", 170 | "foos", 171 | "ilist", 172 | "noexcept", 173 | "pnacl", 174 | "popcnt", 175 | "popcountll", 176 | "prio", 177 | "queryinfo", 178 | "reltgt", 179 | "sarr", 180 | "sarray", 181 | "sched", 182 | "srcloc", 183 | "sringbuffer", 184 | "sset", 185 | "struct", 186 | "sview", 187 | "tgtrel", 188 | "tgts", 189 | "trav", 190 | "twld", 191 | "uncvref", 192 | "upci", 193 | "upos" 194 | ], 195 | "makefile.configureOnOpen": false, 196 | } 197 | } -------------------------------------------------------------------------------- /include/gaia/ecs/chunk_header.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/cnt/bitset.h" 7 | #include "gaia/core/utility.h" 8 | #include "gaia/ecs/archetype_common.h" 9 | #include "gaia/ecs/chunk_allocator.h" 10 | #include "gaia/ecs/component.h" 11 | #include "gaia/ecs/id.h" 12 | 13 | namespace gaia { 14 | namespace ecs { 15 | class World; 16 | class ComponentCache; 17 | struct ComponentCacheItem; 18 | 19 | struct GAIA_API ChunkDataOffsets { 20 | //! Byte at which the first version number is located 21 | ChunkDataVersionOffset firstByte_Versions{}; 22 | //! Byte at which the first entity id is located 23 | ChunkDataOffset firstByte_CompEntities{}; 24 | //! Byte at which the first component id is located 25 | ChunkDataOffset firstByte_Records{}; 26 | //! Byte at which the first entity is located 27 | ChunkDataOffset firstByte_EntityData{}; 28 | }; 29 | 30 | struct GAIA_API ComponentRecord { 31 | //! Component id 32 | Component comp; 33 | //! Pointer to where the first instance of the component is stored 34 | uint8_t* pData; 35 | //! Pointer to component cache record 36 | const ComponentCacheItem* pItem; 37 | }; 38 | 39 | struct GAIA_API ChunkRecords { 40 | //! Pointer to where component versions are stored 41 | ComponentVersion* pVersions{}; 42 | //! Pointer to where (component) entities are stored 43 | Entity* pCompEntities{}; 44 | //! Pointer to the array of component records 45 | ComponentRecord* pRecords{}; 46 | //! Pointer to the array of entities 47 | Entity* pEntities{}; 48 | }; 49 | 50 | struct GAIA_API ChunkHeader final { 51 | static constexpr uint32_t MAX_COMPONENTS_BITS = 5U; 52 | //! Maximum number of components on archetype 53 | static constexpr uint32_t MAX_COMPONENTS = 1U << MAX_COMPONENTS_BITS; 54 | 55 | //! Maximum number of entities per chunk. 56 | //! Defined as sizeof(big_chunk) / sizeof(entity) 57 | static constexpr uint16_t MAX_CHUNK_ENTITIES = (mem_block_size(2) - 64) / sizeof(Entity); 58 | static constexpr uint16_t MAX_CHUNK_ENTITIES_BITS = (uint16_t)core::count_bits(MAX_CHUNK_ENTITIES); 59 | 60 | static constexpr uint16_t CHUNK_LIFESPAN_BITS = 4; 61 | //! Number of ticks before empty chunks are removed 62 | static constexpr uint16_t MAX_CHUNK_LIFESPAN = (1 << CHUNK_LIFESPAN_BITS) - 1; 63 | 64 | //! Parent world 65 | const World* world; 66 | //! Component cache reference 67 | const ComponentCache* cc; 68 | //! Chunk index in its archetype list 69 | uint32_t index; 70 | //! Total number of entities in the chunk. 71 | uint16_t count; 72 | //! Number of enabled entities in the chunk. 73 | uint16_t countEnabled; 74 | //! Capacity (copied from the owner archetype). 75 | uint16_t capacity; 76 | 77 | //! Index of the first enabled entity in the chunk 78 | uint16_t rowFirstEnabledEntity : MAX_CHUNK_ENTITIES_BITS; 79 | //! True if there's any generic component that requires custom construction 80 | uint16_t hasAnyCustomGenCtor : 1; 81 | //! True if there's any unique component that requires custom construction 82 | uint16_t hasAnyCustomUniCtor : 1; 83 | //! True if there's any generic component that requires custom destruction 84 | uint16_t hasAnyCustomGenDtor : 1; 85 | //! True if there's any unique component that requires custom destruction 86 | uint16_t hasAnyCustomUniDtor : 1; 87 | //! When it hits 0 the chunk is scheduled for deletion 88 | uint16_t lifespanCountdown : CHUNK_LIFESPAN_BITS; 89 | //! True if deleted, false otherwise 90 | uint16_t dead : 1; 91 | //! Empty space for future use 92 | uint16_t unused : 11; 93 | 94 | //! Number of generic entities/components 95 | uint8_t genEntities; 96 | //! Number of components on the archetype 97 | uint8_t cntEntities; 98 | //! Version of the world (stable pointer to parent world's world version) 99 | uint32_t& worldVersion; 100 | 101 | static inline uint32_t s_worldVersionDummy = 0; 102 | ChunkHeader(): worldVersion(s_worldVersionDummy) {} 103 | 104 | ChunkHeader( 105 | const World& wld, const ComponentCache& compCache, uint32_t chunkIndex, uint16_t cap, uint8_t genEntitiesCnt, 106 | uint32_t& version): 107 | world(&wld), cc(&compCache), index(chunkIndex), count(0), countEnabled(0), capacity(cap), 108 | // 109 | rowFirstEnabledEntity(0), hasAnyCustomGenCtor(0), hasAnyCustomUniCtor(0), hasAnyCustomGenDtor(0), 110 | hasAnyCustomUniDtor(0), lifespanCountdown(0), dead(0), unused(0), 111 | // 112 | genEntities(genEntitiesCnt), cntEntities(0), worldVersion(version) { 113 | // Make sure the alignment is right 114 | GAIA_ASSERT(uintptr_t(this) % (sizeof(size_t)) == 0); 115 | } 116 | 117 | bool has_disabled_entities() const { 118 | return rowFirstEnabledEntity > 0; 119 | } 120 | 121 | bool has_enabled_entities() const { 122 | return countEnabled > 0; 123 | } 124 | }; 125 | } // namespace ecs 126 | } // namespace gaia 127 | -------------------------------------------------------------------------------- /include/gaia/cnt/fwd_llist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include "gaia/core/iterator.h" 5 | 6 | namespace gaia { 7 | namespace cnt { 8 | template 9 | struct fwd_llist_link { 10 | //! Pointer the the next element 11 | T* next = nullptr; 12 | //! Pointer to the memory address of the previous node's "next". This is not meant for traversal. 13 | //! It merely allows for maintaining the forward links of the list when removing an item, and allows 14 | //! O(1) removals even in a forward list. 15 | T** prevs_next = nullptr; 16 | 17 | //! Returns true if the node is linked with another 18 | GAIA_NODISCARD bool linked() const { 19 | return next != nullptr || prevs_next != nullptr; 20 | } 21 | }; 22 | 23 | //! Each fwd_llist node either has to inherit from fwd_llist_base 24 | //! or it has to provide get_fwd_llist_link() member functions. 25 | template 26 | struct fwd_llist_base { 27 | fwd_llist_link fwd_link_GAIA; 28 | 29 | fwd_llist_link& get_fwd_llist_link() { 30 | return fwd_link_GAIA; 31 | } 32 | const fwd_llist_link& get_fwd_llist_link() const { 33 | return fwd_link_GAIA; 34 | } 35 | }; 36 | 37 | template 38 | struct fwd_llist_iterator { 39 | using iterator_category = core::forward_iterator_tag; 40 | using value_type = T; 41 | using pointer = T*; 42 | using reference = T&; 43 | using difference_type = uint32_t; 44 | using size_type = uint32_t; 45 | using iterator = fwd_llist_iterator; 46 | 47 | private: 48 | T* m_pNode; 49 | 50 | public: 51 | explicit fwd_llist_iterator(T* pNode): m_pNode(pNode) {} 52 | 53 | reference operator*() const { 54 | return *m_pNode; 55 | } 56 | pointer operator->() const { 57 | return m_pNode; 58 | } 59 | 60 | iterator& operator++() { 61 | auto& list = m_pNode->get_fwd_llist_link(); 62 | m_pNode = list.next; 63 | return *this; 64 | } 65 | iterator operator++(int) { 66 | iterator temp(*this); 67 | ++*this; 68 | return temp; 69 | } 70 | 71 | GAIA_NODISCARD bool operator==(const iterator& other) const { 72 | return m_pNode == other.m_pNode; 73 | } 74 | GAIA_NODISCARD bool operator!=(const iterator& other) const { 75 | return m_pNode != other.m_pNode; 76 | } 77 | }; 78 | 79 | //! Forward list container. 80 | //! No memory allocation is performed because the list is stored directly inside allocated nodes. 81 | //! Inserts: O(1) 82 | //! Removals: O(1) 83 | //! Iteration: O(N) 84 | template 85 | struct fwd_llist { 86 | uint32_t count = 0; 87 | T* first = nullptr; 88 | 89 | //! Clears the list. 90 | void clear() { 91 | count = 0; 92 | first = nullptr; 93 | } 94 | 95 | //! Links the node in the list. 96 | void link(T* pNode) { 97 | GAIA_ASSERT(pNode != nullptr); 98 | 99 | auto& link = pNode->get_fwd_llist_link(); 100 | link.next = first; 101 | if (first != nullptr) { 102 | auto& linkFirst = first->get_fwd_llist_link(); 103 | linkFirst.prevs_next = &(link.next); 104 | first = pNode; 105 | } 106 | link.prevs_next = &first; 107 | first = pNode; 108 | 109 | ++count; 110 | } 111 | 112 | //! Unlinks the node from the list. 113 | void unlink(T* pNode) { 114 | GAIA_ASSERT(pNode != nullptr); 115 | 116 | auto& link = pNode->get_fwd_llist_link(); 117 | *(link.prevs_next) = link.next; 118 | if (link.next != nullptr) { 119 | auto& linkNext = link.next->get_fwd_llist_link(); 120 | linkNext.prevs_next = link.prevs_next; 121 | } 122 | 123 | // Reset the node's link 124 | link = {}; 125 | 126 | --count; 127 | } 128 | 129 | //! Checks if the node \param pNode is linked in the list. 130 | GAIA_NODISCARD bool has(T* pNode) const { 131 | GAIA_ASSERT(pNode != nullptr); 132 | 133 | for (auto& curr: *this) { 134 | if (&curr == pNode) 135 | return true; 136 | } 137 | 138 | return false; 139 | } 140 | 141 | //! Returns true if the list is empty. False otherwise. 142 | GAIA_NODISCARD bool empty() const { 143 | GAIA_ASSERT(count == 0); 144 | return first == nullptr; 145 | } 146 | 147 | //! Returns the number of nodes linked in the list. 148 | GAIA_NODISCARD uint32_t size() const { 149 | return count; 150 | } 151 | 152 | fwd_llist_iterator begin() { 153 | return fwd_llist_iterator(first); 154 | } 155 | 156 | fwd_llist_iterator begin() const { 157 | return fwd_llist_iterator((const T*)first); 158 | } 159 | 160 | fwd_llist_iterator cbegin() const { 161 | return fwd_llist_iterator((const T*)first); 162 | } 163 | 164 | fwd_llist_iterator end() { 165 | return fwd_llist_iterator(nullptr); 166 | } 167 | 168 | fwd_llist_iterator end() const { 169 | return fwd_llist_iterator((const T*)nullptr); 170 | } 171 | 172 | fwd_llist_iterator cend() const { 173 | return fwd_llist_iterator((const T*)nullptr); 174 | } 175 | }; 176 | } // namespace cnt 177 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/archetype_graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/cnt/map.h" 7 | #include "gaia/ecs/api.h" 8 | #include "gaia/ecs/archetype_common.h" 9 | #include "gaia/ecs/component.h" 10 | #include "gaia/ecs/id.h" 11 | #include "gaia/util/logging.h" 12 | 13 | namespace gaia { 14 | namespace ecs { 15 | class World; 16 | 17 | using ArchetypeGraphEdge = ArchetypeIdHashPair; 18 | 19 | class ArchetypeGraph { 20 | using EdgeMap = cnt::map; 21 | 22 | //! Map of edges in the archetype graph when adding components 23 | EdgeMap m_edgesAdd; 24 | //! Map of edges in the archetype graph when removing components 25 | EdgeMap m_edgesDel; 26 | 27 | private: 28 | void add_edge(EdgeMap& edges, Entity entity, ArchetypeId archetypeId, ArchetypeIdHash hash) { 29 | #if GAIA_ASSERT_ENABLED 30 | const auto ret = 31 | #endif 32 | edges.try_emplace(EntityLookupKey(entity), ArchetypeGraphEdge{archetypeId, hash}); 33 | #if GAIA_ASSERT_ENABLED 34 | // If the result already exists make sure the new one is the same 35 | if (!ret.second) { 36 | const auto it = edges.find(EntityLookupKey(entity)); 37 | GAIA_ASSERT(it != edges.end()); 38 | GAIA_ASSERT(it->second.id == archetypeId); 39 | GAIA_ASSERT(it->second.hash == hash); 40 | } 41 | #endif 42 | } 43 | 44 | void del_edge(EdgeMap& edges, Entity entity) { 45 | edges.erase(EntityLookupKey(entity)); 46 | } 47 | 48 | GAIA_NODISCARD ArchetypeGraphEdge find_edge(const EdgeMap& edges, Entity entity) const { 49 | const auto it = edges.find(EntityLookupKey(entity)); 50 | return it != edges.end() ? it->second : ArchetypeIdHashPairBad; 51 | } 52 | 53 | public: 54 | //! Creates an "add" edge in the graph leading to the target archetype. 55 | //! \param entity Edge entity. 56 | //! \param archetypeId Target archetype. 57 | //! \param hash Archetype hash. 58 | void add_edge_right(Entity entity, ArchetypeId archetypeId, ArchetypeIdHash hash) { 59 | add_edge(m_edgesAdd, entity, archetypeId, hash); 60 | } 61 | 62 | //! Creates a "del" edge in the graph leading to the target archetype. 63 | //! \param entity Edge entity. 64 | //! \param archetypeId Target archetype. 65 | //! \param hash Archetype hash. 66 | void add_edge_left(Entity entity, ArchetypeId archetypeId, ArchetypeIdHash hash) { 67 | add_edge(m_edgesDel, entity, archetypeId, hash); 68 | } 69 | 70 | //! Deletes the "add" edge formed by the entity @a entity. 71 | //! \param entity Edge entity. 72 | void del_edge_right(Entity entity) { 73 | del_edge(m_edgesAdd, entity); 74 | } 75 | 76 | //! Deletes the "del" edge formed by the entity @a entity. 77 | //! \param entity Edge entity. 78 | void del_edge_left(Entity entity) { 79 | del_edge(m_edgesDel, entity); 80 | } 81 | 82 | //! Checks if an archetype graph "add" edge with entity @a entity exists. 83 | //! \param entity Edge entity. 84 | //! \return Archetype id of the target archetype if the edge is found. ArchetypeGraphEdgeBad otherwise. 85 | GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const { 86 | return find_edge(m_edgesAdd, entity); 87 | } 88 | 89 | //! Checks if an archetype graph "del" edge with entity @a entity exists. 90 | //! \param entity Edge entity. 91 | //! \return Archetype id of the target archetype if the edge is found. ArchetypeGraphEdgeBad otherwise. 92 | GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const { 93 | return find_edge(m_edgesDel, entity); 94 | } 95 | 96 | GAIA_NODISCARD auto& right_edges() { 97 | return m_edgesAdd; 98 | } 99 | 100 | GAIA_NODISCARD const auto& right_edges() const { 101 | return m_edgesAdd; 102 | } 103 | 104 | GAIA_NODISCARD auto& left_edges() { 105 | return m_edgesDel; 106 | } 107 | 108 | GAIA_NODISCARD const auto& left_edges() const { 109 | return m_edgesDel; 110 | } 111 | 112 | void diag(const World& world) const { 113 | auto diagEdge = [&](const auto& edges) { 114 | for (const auto& edge: edges) { 115 | const auto entity = edge.first.entity(); 116 | if (entity.pair()) { 117 | const auto* name0 = entity_name(world, entity.id()); 118 | const auto* name1 = entity_name(world, entity.gen()); 119 | GAIA_LOG_N( 120 | " pair [%u:%u], %s -> %s, aid:%u", 121 | // 122 | entity.id(), entity.gen(), name0, name1, edge.second.id); 123 | } else { 124 | const auto* name = entity_name(world, entity); 125 | GAIA_LOG_N( 126 | " ent [%u:%u], %s [%s], aid:%u", 127 | // 128 | entity.id(), entity.gen(), name, EntityKindString[entity.kind()], edge.second.id); 129 | } 130 | } 131 | }; 132 | 133 | // Add edges (movement towards the leafs) 134 | if (!m_edgesAdd.empty()) { 135 | GAIA_LOG_N(" Add edges - count:%u", (uint32_t)m_edgesAdd.size()); 136 | diagEdge(m_edgesAdd); 137 | } 138 | 139 | // Delete edges (movement towards the root) 140 | if (!m_edgesDel.empty()) { 141 | GAIA_LOG_N(" Del edges - count:%u", (uint32_t)m_edgesDel.size()); 142 | diagEdge(m_edgesDel); 143 | } 144 | } 145 | }; 146 | } // namespace ecs 147 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ser/ser_rt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "gaia/core/utility.h" 8 | #include "gaia/meta/reflection.h" 9 | #include "gaia/ser/ser_common.h" 10 | 11 | namespace gaia { 12 | namespace ser { 13 | struct ISerializer { 14 | ISerializer() = default; 15 | virtual ~ISerializer() = default; 16 | ISerializer(const ISerializer&) = default; 17 | ISerializer(ISerializer&&) = default; 18 | ISerializer& operator=(const ISerializer&) = default; 19 | ISerializer& operator=(ISerializer&&) = default; 20 | 21 | template 22 | void save(const T& arg) { 23 | using U = core::raw_t; 24 | 25 | // Custom save() has precedence 26 | if constexpr (has_func_save::value) { 27 | arg.save(*this); 28 | } else if constexpr (has_tag_save::value) { 29 | tag_invoke(save_v, *this, static_cast(arg)); 30 | } 31 | // Trivially serializable types 32 | else if constexpr (is_trivially_serializable::value) { 33 | this->save_raw(arg); 34 | } 35 | // Types which have size(), begin() and end() member functions 36 | else if constexpr (core::has_size_begin_end::value) { 37 | const auto size = arg.size(); 38 | this->save_raw(size); 39 | 40 | for (const auto& e: std::as_const(arg)) 41 | save(e); 42 | } 43 | // Classes 44 | else if constexpr (std::is_class_v) { 45 | meta::each_member(GAIA_FWD(arg), [&](auto&&... items) { 46 | // TODO: Handle contiguous blocks of trivially copyable types 47 | (save(items), ...); 48 | }); 49 | } else 50 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 51 | } 52 | 53 | template 54 | void load(T& arg) { 55 | using U = core::raw_t; 56 | 57 | // Custom load() has precedence 58 | if constexpr (has_func_load::value) { 59 | arg.load(*this); 60 | } else if constexpr (has_tag_load::value) { 61 | tag_invoke(load_v, *this, static_cast(arg)); 62 | } 63 | // Trivially serializable types 64 | else if constexpr (is_trivially_serializable::value) { 65 | this->load_raw(arg); 66 | } 67 | // Types which have size(), begin() and end() member functions 68 | else if constexpr (core::has_size_begin_end::value) { 69 | auto size = arg.size(); 70 | this->load_raw(size); 71 | 72 | if constexpr (has_func_resize::value) { 73 | // If resize is present, use it 74 | arg.resize(size); 75 | 76 | // NOTE: We can't do it this way. If there are containers with the overloaded 77 | // operator=, the result might not be what one would expect. 78 | // E.g., in our case, SoA containers need specific handling. 79 | // for (auto&& e: arg) 80 | // load(e); 81 | 82 | uint32_t i = 0; 83 | for (auto e: arg) { 84 | load(e); 85 | arg[i] = std::move(e); 86 | ++i; 87 | } 88 | } else { 89 | // With no resize present, write directly into memory 90 | GAIA_FOR(size) { 91 | using arg_type = typename std::remove_pointer::type; 92 | arg_type val; 93 | load(val); 94 | arg[i] = val; 95 | } 96 | } 97 | } 98 | // Classes 99 | else if constexpr (std::is_class_v) { 100 | meta::each_member(GAIA_FWD(arg), [&](auto&&... items) { 101 | // TODO: Handle contiguous blocks of trivially copyable types 102 | (load(items), ...); 103 | }); 104 | } else 105 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 106 | } 107 | 108 | #if GAIA_ASSERT_ENABLED 109 | template 110 | void check(const T& arg) { 111 | T tmp{}; 112 | 113 | // Make sure that we write just as many bytes as we read. 114 | // If positions are the same there is a good chance that save and load match. 115 | const auto pos0 = tell(); 116 | save(arg); 117 | const auto pos1 = tell(); 118 | seek(pos0); 119 | load(tmp); 120 | const auto pos2 = tell(); 121 | GAIA_ASSERT(pos2 == pos1); 122 | 123 | // Return back to the original position in the buffer. 124 | seek(pos0); 125 | } 126 | #endif 127 | 128 | template 129 | void save_raw(const T& value) { 130 | save_raw(&value, sizeof(value), ser::type_id()); 131 | } 132 | 133 | template 134 | void load_raw(T& value) { 135 | load_raw(&value, sizeof(value), ser::type_id()); 136 | } 137 | 138 | virtual void save_raw(const void* src, uint32_t size, serialization_type_id id) { 139 | (void)id; 140 | (void)src; 141 | (void)size; 142 | } 143 | 144 | virtual void load_raw(void* src, uint32_t size, serialization_type_id id) { 145 | (void)id; 146 | (void)src; 147 | (void)size; 148 | } 149 | 150 | virtual const char* data() const { 151 | return nullptr; 152 | } 153 | 154 | virtual void reset() {} 155 | 156 | virtual uint32_t tell() const { 157 | return 0; 158 | } 159 | 160 | virtual uint32_t bytes() const { 161 | return 0; 162 | } 163 | 164 | virtual void seek(uint32_t pos) { 165 | (void)pos; 166 | } 167 | }; 168 | } // namespace ser 169 | } // namespace gaia 170 | -------------------------------------------------------------------------------- /include/gaia/ser/ser_buffer_binary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/cnt/darray.h" 7 | #include "gaia/cnt/darray_ext.h" 8 | 9 | namespace gaia { 10 | namespace ser { 11 | enum class serialization_type_id : uint8_t; 12 | 13 | namespace detail { 14 | static constexpr uint32_t SerializationBufferCapacityIncreaseSize = 128U; 15 | 16 | template 17 | class ser_buffer_binary_impl { 18 | protected: 19 | // Increase the capacity by multiples of CapacityIncreaseSize 20 | static constexpr uint32_t CapacityIncreaseSize = SerializationBufferCapacityIncreaseSize; 21 | 22 | //! Buffer holding raw data 23 | DataContainer m_data; 24 | //! Current position in the buffer 25 | uint32_t m_dataPos = 0; 26 | 27 | public: 28 | void reset() { 29 | m_dataPos = 0; 30 | m_data.clear(); 31 | } 32 | 33 | //! Returns the number of bytes written in the buffer 34 | GAIA_NODISCARD uint32_t bytes() const { 35 | return (uint32_t)m_data.size(); 36 | } 37 | 38 | //! Returns true if there is no data written in the buffer 39 | GAIA_NODISCARD bool empty() const { 40 | return m_data.empty(); 41 | } 42 | 43 | //! Returns the pointer to the data in the buffer 44 | GAIA_NODISCARD const auto* data() const { 45 | return m_data.data(); 46 | } 47 | 48 | //! Makes sure there is enough capacity in our data container to hold another @a size bytes of data. 49 | //! \param size Minimum number of free bytes at the end of the buffer. 50 | void reserve(uint32_t size) { 51 | const auto nextSize = m_dataPos + size; 52 | if (nextSize <= bytes()) 53 | return; 54 | 55 | // Make sure there is enough capacity to hold our data 56 | const auto newSize = bytes() + size; 57 | const auto newCapacity = ((newSize / CapacityIncreaseSize) * CapacityIncreaseSize) + CapacityIncreaseSize; 58 | m_data.reserve(newCapacity); 59 | } 60 | 61 | //! Resizes the internal buffer to @a size bytes. 62 | //! \param size Position in the buffer to move to. 63 | void resize(uint32_t size) { 64 | m_data.resize(size); 65 | } 66 | 67 | //! Changes the current position in the buffer. 68 | //! \param pos Position in the buffer to move to. 69 | void seek(uint32_t pos) { 70 | m_dataPos = pos; 71 | } 72 | 73 | //! Advances @a size bytes from the current buffer position. 74 | //! \param size Number of bytes to skip 75 | void skip(uint32_t size) { 76 | m_dataPos += size; 77 | } 78 | 79 | //! Returns the current position in the buffer 80 | GAIA_NODISCARD uint32_t tell() const { 81 | return m_dataPos; 82 | } 83 | 84 | //! Writes @a value to the buffer 85 | //! \param value Value to store 86 | template 87 | void save(T&& value) { 88 | reserve((uint32_t)sizeof(T)); 89 | 90 | const auto cnt = m_dataPos + (uint32_t)sizeof(T); 91 | if (cnt > m_data.size()) 92 | m_data.resize(cnt); 93 | mem::unaligned_ref mem((void*)&m_data[m_dataPos]); 94 | mem = GAIA_FWD(value); 95 | 96 | m_dataPos += (uint32_t)sizeof(T); 97 | } 98 | 99 | //! Writes @a size bytes of data starting at the address @a pSrc to the buffer 100 | //! \param pSrc Pointer to serialized data 101 | //! \param size Size of serialized data in bytes 102 | //! \param id Type of serialized data 103 | void save_raw(const void* pSrc, uint32_t size, [[maybe_unused]] ser::serialization_type_id id) { 104 | if (size == 0) 105 | return; 106 | 107 | reserve(size); 108 | 109 | // Copy "size" bytes of raw data starting at pSrc 110 | const auto cnt = m_dataPos + size; 111 | if (cnt > m_data.size()) 112 | m_data.resize(cnt); 113 | memcpy((void*)&m_data[m_dataPos], pSrc, size); 114 | 115 | m_dataPos += size; 116 | } 117 | 118 | //! Loads @a value from the buffer 119 | //! \param[out] value Value to load 120 | template 121 | void load(T& value) { 122 | GAIA_ASSERT(m_dataPos + (uint32_t)sizeof(T) <= bytes()); 123 | 124 | const auto& cdata = std::as_const(m_data); 125 | value = mem::unaligned_ref((void*)&cdata[m_dataPos]); 126 | 127 | m_dataPos += (uint32_t)sizeof(T); 128 | } 129 | 130 | //! Loads @a size bytes of data from the buffer and writes it to the address @a pDst 131 | //! \param[out] pDst Pointer to where deserialized data is written 132 | //! \param size Size of serialized data in bytes 133 | //! \param id Type of serialized data 134 | void load_raw(void* pDst, uint32_t size, [[maybe_unused]] ser::serialization_type_id id) { 135 | if (size == 0) 136 | return; 137 | 138 | GAIA_ASSERT(m_dataPos + size <= bytes()); 139 | 140 | const auto& cdata = std::as_const(m_data); 141 | memmove(pDst, (const void*)&cdata[m_dataPos], size); 142 | 143 | m_dataPos += size; 144 | } 145 | }; 146 | } // namespace detail 147 | 148 | using ser_buffer_binary_storage = gaia::cnt::darray_ext; 149 | using ser_buffer_binary_storage_dyn = gaia::cnt::darray; 150 | 151 | //! Minimal binary serializer meant to runtime data. 152 | //! It does not offer any versioning, or type information. 153 | class ser_buffer_binary: public detail::ser_buffer_binary_impl {}; 154 | class ser_buffer_binary_dyn: public detail::ser_buffer_binary_impl {}; 155 | } // namespace ser 156 | } // namespace gaia 157 | -------------------------------------------------------------------------------- /include/gaia/mem/stack_allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | 6 | #include "gaia/mem/raw_data_holder.h" 7 | 8 | namespace gaia { 9 | namespace mem { 10 | namespace detail { 11 | struct AllocationInfo { 12 | //! Byte offset of the previous allocation 13 | uint32_t prev; 14 | //! Offset of data area from info area in bytes 15 | uint32_t off : 8; 16 | //! The number of requested bytes to allocate 17 | uint32_t cnt : 24; 18 | void (*dtor)(void*, uint32_t); 19 | }; 20 | } // namespace detail 21 | 22 | // MSVC might warn about applying additional padding to an instance of StackAllocator. 23 | // This is perfectly fine, but might make builds with warning-as-error turned on to fail. 24 | GAIA_MSVC_WARNING_PUSH() 25 | GAIA_MSVC_WARNING_DISABLE(4324) 26 | 27 | //! Stack allocator capable of instantiating any default-constructible object on stack. 28 | //! Every allocation comes with a 16-bytes long sentinel object. 29 | template 30 | class StackAllocator { 31 | using alloc_info = detail::AllocationInfo; 32 | 33 | //! Internal stack buffer aligned to 16B boundary 34 | detail::raw_data_holder m_buffer; 35 | //! Current byte offset 36 | uint32_t m_pos = 0; 37 | //! Byte offset of the previous allocation 38 | uint32_t m_posPrev = 0; 39 | //! Number of allocations made 40 | uint32_t m_allocs = 0; 41 | 42 | public: 43 | StackAllocator() { 44 | // Aligned used so the sentinel object can be stored properly 45 | const auto bufferMemAddr = (uintptr_t)((uint8_t*)m_buffer); 46 | m_posPrev = m_pos = padding(bufferMemAddr); 47 | } 48 | 49 | ~StackAllocator() { 50 | reset(); 51 | } 52 | 53 | StackAllocator(const StackAllocator&) = delete; 54 | StackAllocator(StackAllocator&&) = delete; 55 | StackAllocator& operator=(const StackAllocator&) = delete; 56 | StackAllocator& operator=(StackAllocator&&) = delete; 57 | 58 | //! Allocates \param cnt objects of type \tparam T inside the buffer. 59 | //! No default initialization is done so the object is returned in a non-initialized 60 | //! state unless a custom constructor is provided. 61 | //! \return Pointer to the first allocated object 62 | template 63 | GAIA_NODISCARD T* alloc(uint32_t cnt) { 64 | constexpr auto sizeT = (uint32_t)sizeof(T); 65 | const auto addrBuff = (uintptr_t)((uint8_t*)m_buffer); 66 | const auto addrAllocInfo = align(addrBuff + m_pos); 67 | const auto addrAllocData = align(addrAllocInfo + sizeof(alloc_info)); 68 | const auto off = (uint32_t)(addrAllocData - addrAllocInfo); 69 | 70 | // There has to be some space left in the buffer 71 | const bool isFull = (uint32_t)(addrAllocData - addrBuff) + sizeT * cnt >= CapacityInBytes; 72 | if GAIA_UNLIKELY (isFull) { 73 | GAIA_ASSERT(!isFull && "Allocation space exceeded on StackAllocator"); 74 | return nullptr; 75 | } 76 | 77 | // Memory sentinel 78 | auto* pInfo = (alloc_info*)addrAllocInfo; 79 | pInfo->prev = m_posPrev; 80 | pInfo->off = off; 81 | pInfo->cnt = cnt; 82 | pInfo->dtor = [](void* ptr, uint32_t cnt) { 83 | core::call_dtor_n((T*)ptr, cnt); 84 | }; 85 | 86 | // Constructing the object is necessary 87 | auto* pData = (T*)addrAllocData; 88 | core::call_ctor_raw_n(pData, cnt); 89 | 90 | // Allocation start offset 91 | m_posPrev = (uint32_t)(addrAllocInfo - addrBuff); 92 | // Point to the next free space (not necessary aligned yet) 93 | m_pos = m_posPrev + pInfo->off + sizeT * cnt; 94 | 95 | ++m_allocs; 96 | return pData; 97 | } 98 | 99 | //! Frees the last allocated object from the stack. 100 | //! \param pData Pointer to the last allocated object on the stack 101 | //! \param cnt Number of objects that were allocated on the given memory address 102 | void free([[maybe_unused]] void* pData, [[maybe_unused]] uint32_t cnt) { 103 | GAIA_ASSERT(pData != nullptr); 104 | GAIA_ASSERT(cnt > 0); 105 | GAIA_ASSERT(m_allocs > 0); 106 | 107 | const auto addrBuff = (uintptr_t)((uint8_t*)m_buffer); 108 | 109 | // Destroy the last allocated object 110 | const auto addrAllocInfo = addrBuff + m_posPrev; 111 | auto* pInfo = (alloc_info*)addrAllocInfo; 112 | const auto addrAllocData = addrAllocInfo + pInfo->off; 113 | void* pInfoData = (void*)addrAllocData; 114 | GAIA_ASSERT(pData == pInfoData); 115 | GAIA_ASSERT(pInfo->cnt == cnt); 116 | pInfo->dtor(pInfoData, pInfo->cnt); 117 | 118 | m_pos = m_posPrev; 119 | m_posPrev = pInfo->prev; 120 | --m_allocs; 121 | } 122 | 123 | //! Frees all allocated objects from the buffer 124 | void reset() { 125 | const auto addrBuff = (uintptr_t)((uint8_t*)m_buffer); 126 | 127 | // Destroy allocated objects back-to-front 128 | auto pos = m_posPrev; 129 | while (m_allocs > 0) { 130 | const auto addrAllocInfo = addrBuff + pos; 131 | auto* pInfo = (alloc_info*)addrAllocInfo; 132 | const auto addrAllocData = addrAllocInfo + pInfo->off; 133 | pInfo->dtor((void*)addrAllocData, pInfo->cnt); 134 | pos = pInfo->prev; 135 | 136 | --m_allocs; 137 | } 138 | 139 | GAIA_ASSERT(m_allocs == 0); 140 | 141 | m_pos = 0; 142 | m_posPrev = 0; 143 | m_allocs = 0; 144 | } 145 | 146 | GAIA_NODISCARD constexpr uint32_t capacity() { 147 | return CapacityInBytes; 148 | } 149 | }; 150 | 151 | GAIA_MSVC_WARNING_POP() 152 | } // namespace mem 153 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ser/ser_ct.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "gaia/core/utility.h" 8 | #include "gaia/meta/reflection.h" 9 | #include "gaia/ser/ser_common.h" 10 | 11 | namespace gaia { 12 | namespace ser { 13 | namespace detail { 14 | template 15 | void save_one(Writer& s, const T& arg) { 16 | using U = core::raw_t; 17 | 18 | // Custom save() has precedence 19 | if constexpr (has_func_save::value) { 20 | arg.save(s); 21 | } else if constexpr (has_tag_save::value) { 22 | tag_invoke(save_v, s, static_cast(arg)); 23 | } 24 | // Trivially serializable types 25 | else if constexpr (is_trivially_serializable::value) { 26 | s.save(arg); 27 | } 28 | // Types which have size(), begin() and end() member functions 29 | else if constexpr (core::has_size_begin_end::value) { 30 | const auto size = arg.size(); 31 | s.save(size); 32 | 33 | for (const auto& e: std::as_const(arg)) 34 | save_one(s, e); 35 | } 36 | // Classes 37 | else if constexpr (std::is_class_v) { 38 | meta::each_member(GAIA_FWD(arg), [&s](auto&&... items) { 39 | // TODO: Handle contiguous blocks of trivially copyable types 40 | (save_one(s, items), ...); 41 | }); 42 | } else 43 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 44 | } 45 | 46 | template 47 | void load_one(Reader& s, T& arg) { 48 | using U = core::raw_t; 49 | 50 | // Custom load() has precedence 51 | if constexpr (has_func_load::value) { 52 | arg.load(s); 53 | } else if constexpr (has_tag_load::value) { 54 | tag_invoke(load_v, s, static_cast(arg)); 55 | } 56 | // Trivially serializable types 57 | else if constexpr (is_trivially_serializable::value) { 58 | s.load(arg); 59 | } 60 | // Types which have size(), begin() and end() member functions 61 | else if constexpr (core::has_size_begin_end::value) { 62 | auto size = arg.size(); 63 | s.load(size); 64 | 65 | if constexpr (has_func_resize::value) { 66 | // If resize is present, use it 67 | arg.resize(size); 68 | 69 | // NOTE: We can't do it this way. If there are containers with the overloaded 70 | // operator=, the result might not be what one would expect. 71 | // E.g., in our case, SoA containers need specific handling. 72 | // for (auto&& e: arg) 73 | // load_one(s, e); 74 | 75 | uint32_t i = 0; 76 | for (auto e: arg) { 77 | load_one(s, e); 78 | arg[i] = std::move(e); 79 | ++i; 80 | } 81 | } else { 82 | // With no resize present, write directly into memory 83 | GAIA_FOR(size) { 84 | using arg_type = typename std::remove_pointer::type; 85 | arg_type val; 86 | load_one(s, val); 87 | arg[i] = val; 88 | } 89 | } 90 | } 91 | // Classes 92 | else if constexpr (std::is_class_v) { 93 | meta::each_member(GAIA_FWD(arg), [&s](auto&&... items) { 94 | // TODO: Handle contiguous blocks of trivially copyable types 95 | (load_one(s, items), ...); 96 | }); 97 | } else 98 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 99 | } 100 | 101 | #if GAIA_ASSERT_ENABLED 102 | template 103 | void check_one(Writer& s, const T& arg) { 104 | T tmp{}; 105 | 106 | // Make sure that we write just as many bytes as we read. 107 | // If the positions are the same there is a good chance that save and load match. 108 | const auto pos0 = s.tell(); 109 | save_one(s, arg); 110 | const auto pos1 = s.tell(); 111 | s.seek(pos0); 112 | load_one(s, tmp); 113 | GAIA_ASSERT(s.tell() == pos1); 114 | 115 | // Return back to the original position in the buffer. 116 | s.seek(pos0); 117 | } 118 | #endif 119 | } // namespace detail 120 | 121 | //! Write @a data using @a Writer at compile-time. 122 | //! \tparam Writer Type of writer 123 | //! \param writer Writer used for serialization 124 | //! \param data Data to serialize 125 | //! \warning Writer has to implement a save function as follows: 126 | //! `template void save(const T& arg);` 127 | template 128 | void save(Writer& writer, const T& data) { 129 | detail::save_one(writer, data); 130 | } 131 | 132 | //! Read @a data using @a Reader at compile-time. 133 | //! \tparam Reader Type of reader 134 | //! \param reader Reader used for deserialization 135 | //! \param[out] data Data to deserialize 136 | //! \warning Reader has to implement a save function as follows: 137 | //! `template void load(T& arg);` 138 | template 139 | void load(Reader& reader, T& data) { 140 | detail::load_one(reader, data); 141 | } 142 | 143 | #if GAIA_ASSERT_ENABLED 144 | //! Write \param data using \tparam Writer at compile-time, then read it afterwards. 145 | //! Used to verify that both save and load work correctly. 146 | //! 147 | //! \warning Writer has to implement a save function as follows: 148 | //! template void save(const T& arg); 149 | //! \warning Reader has to implement a save function as follows: 150 | //! template void load(T& arg); 151 | template 152 | void check(Writer& writer, const T& data) { 153 | detail::check_one(writer, data); 154 | } 155 | #endif 156 | } // namespace ser 157 | } // namespace gaia 158 | -------------------------------------------------------------------------------- /include/gaia/ecs/component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "gaia/core/hashing_policy.h" 8 | #include "gaia/core/utility.h" 9 | #include "gaia/ecs/id.h" 10 | #include "gaia/mem/data_layout_policy.h" 11 | #include "gaia/meta/type_info.h" 12 | 13 | namespace gaia { 14 | namespace ecs { 15 | //---------------------------------------------------------------------- 16 | // Component-related types 17 | //---------------------------------------------------------------------- 18 | 19 | using ComponentVersion = uint32_t; 20 | using ChunkDataVersionOffset = uint8_t; 21 | using CompOffsetMappingIndex = uint8_t; 22 | using ChunkDataOffset = uint16_t; 23 | using ComponentLookupHash = core::direct_hash_key; 24 | using EntitySpan = std::span; 25 | using EntitySpanMut = std::span; 26 | using ComponentSpan = std::span; 27 | using ChunkDataOffsetSpan = std::span; 28 | using SortComponentCond = core::is_smaller; 29 | 30 | //---------------------------------------------------------------------- 31 | // Component storage 32 | //---------------------------------------------------------------------- 33 | 34 | enum class DataStorageType : uint32_t { 35 | Table, //< Data stored in a table 36 | Sparse, //< Data stored in a sparse set 37 | 38 | Count = 2 39 | }; 40 | 41 | #ifndef GAIA_STORAGE 42 | #define GAIA_STORAGE(storage_name) \ 43 | static constexpr auto gaia_Storage_Type = ::gaia::ecs::DataStorageType::storage_name 44 | #endif 45 | 46 | namespace detail { 47 | template 48 | struct storage_type { 49 | static constexpr DataStorageType value = DataStorageType::Table; 50 | }; 51 | template 52 | struct storage_type> { 53 | static constexpr DataStorageType value = T::gaia_Storage_Type; 54 | }; 55 | } // namespace detail 56 | 57 | template 58 | inline constexpr DataStorageType storage_type_v = detail::storage_type::value; 59 | 60 | //---------------------------------------------------------------------- 61 | // Component verification 62 | //---------------------------------------------------------------------- 63 | 64 | namespace detail { 65 | template 66 | struct is_component_size_valid: std::bool_constant {}; 67 | 68 | template 69 | struct is_component_type_valid: 70 | std::bool_constant< 71 | // SoA types need to be trivial. No restrictions otherwise. 72 | (!mem::is_soa_layout_v || std::is_trivially_copyable_v)> {}; 73 | } // namespace detail 74 | 75 | //---------------------------------------------------------------------- 76 | // Component verification 77 | //---------------------------------------------------------------------- 78 | 79 | template 80 | constexpr void verify_comp() { 81 | using U = typename actual_type_t::TypeOriginal; 82 | 83 | // Make sure we only use this for "raw" types 84 | static_assert( 85 | core::is_raw_v, 86 | "Components have to be \"raw\" types - no arrays, no const, reference, pointer or volatile"); 87 | } 88 | 89 | //---------------------------------------------------------------------- 90 | // Component lookup hash 91 | //---------------------------------------------------------------------- 92 | 93 | template 94 | GAIA_NODISCARD constexpr ComponentLookupHash calc_lookup_hash(Container arr) noexcept { 95 | constexpr auto arrSize = arr.size(); 96 | if constexpr (arrSize == 0) { 97 | return {0}; 98 | } else { 99 | ComponentLookupHash::Type hash = arr[0]; 100 | core::each([&hash, &arr](auto i) { 101 | hash = core::hash_combine(hash, arr[i + 1]); 102 | }); 103 | return {hash}; 104 | } 105 | } 106 | 107 | template 108 | constexpr ComponentLookupHash calc_lookup_hash() noexcept; 109 | 110 | template 111 | GAIA_NODISCARD constexpr ComponentLookupHash calc_lookup_hash() noexcept { 112 | if constexpr (sizeof...(Rest) == 0) 113 | return {meta::type_info::hash()}; 114 | else 115 | return {core::hash_combine(meta::type_info::hash(), meta::type_info::hash()...)}; 116 | } 117 | 118 | template <> 119 | GAIA_NODISCARD constexpr ComponentLookupHash calc_lookup_hash() noexcept { 120 | return {0}; 121 | } 122 | 123 | //! Calculates a lookup hash from the provided entities 124 | //! \param comps Span of entities 125 | //! \return Lookup hash 126 | GAIA_NODISCARD inline ComponentLookupHash calc_lookup_hash(EntitySpan comps) noexcept { 127 | const auto compsSize = comps.size(); 128 | if (compsSize == 0) 129 | return {0}; 130 | 131 | auto hash = core::calculate_hash64(comps[0].value()); 132 | GAIA_FOR2(1, compsSize) { 133 | hash = core::hash_combine(hash, core::calculate_hash64(comps[i].value())); 134 | } 135 | return {hash}; 136 | } 137 | 138 | //! Located the index at which the provided component id is located in the component array 139 | //! \param pComps Pointer to the start of the component array 140 | //! \param entity Entity we search for 141 | //! \return Index of the component id in the array 142 | //! \warning The component id must be present in the array 143 | template 144 | GAIA_NODISCARD inline uint32_t comp_idx(const Entity* pComps, Entity entity) { 145 | // We let the compiler know the upper iteration bound at compile-time. 146 | // This way it can optimize better (e.g. loop unrolling, vectorization). 147 | GAIA_FOR(MAX_COMPONENTS) { 148 | if (pComps[i] == entity) 149 | return i; 150 | } 151 | 152 | GAIA_ASSERT(false); 153 | return BadIndex; 154 | } 155 | 156 | //! Located the index at which the provided component id is located in the component array 157 | //! \param comps Component view to search in 158 | //! \param entity Entity we search for 159 | //! \warning The component id must be present in the array 160 | GAIA_NODISCARD inline uint32_t comp_idx(std::span comps, Entity entity) { 161 | // We let the compiler know the upper iteration bound at compile-time. 162 | // This way it can optimize better (e.g. loop unrolling, vectorization). 163 | const auto cnt = (uint32_t)comps.size(); 164 | GAIA_FOR(cnt) { 165 | if (comps[i] == entity) 166 | return i; 167 | } 168 | 169 | GAIA_ASSERT(false); 170 | return BadIndex; 171 | } 172 | } // namespace ecs 173 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ser/ser_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "gaia/core/utility.h" 8 | 9 | namespace gaia { 10 | namespace ser { 11 | enum class serialization_type_id : uint8_t { 12 | // Dummy 13 | ignore = 0, 14 | 15 | // Integer types 16 | s8 = 1, 17 | u8 = 2, 18 | s16 = 3, 19 | u16 = 4, 20 | s32 = 5, 21 | u32 = 6, 22 | s64 = 7, 23 | u64 = 8, 24 | 25 | // Boolean 26 | b = 9, 27 | 28 | // Character types 29 | c8 = 10, 30 | c16 = 11, 31 | c32 = 12, 32 | cw = 13, 33 | 34 | // Floating point types 35 | f8 = 14, 36 | f16 = 15, 37 | f32 = 16, 38 | f64 = 17, 39 | f128 = 18, 40 | 41 | // Special 42 | special_begin = 19, 43 | trivial_wrapper = special_begin, 44 | data_and_size = 20, 45 | 46 | Last = data_and_size, 47 | }; 48 | 49 | inline uint32_t serialization_type_size(serialization_type_id id, uint32_t size) { 50 | static const uint32_t sizes[] = { 51 | // Dummy 52 | 0, // ignore 53 | 54 | // Integer types 55 | 1, // s8 56 | 1, // u8 57 | 2, // s16 58 | 2, // u16 59 | 4, // s32 60 | 4, // u32 61 | 8, // s64 62 | 8, // u64 63 | 64 | // Boolean 65 | 1, // b 66 | 67 | // Character types 68 | 1, // c8 69 | 2, // c16 70 | 4, // c32 71 | 8, // cw 72 | 73 | // Floating point types 74 | 1, // f8 75 | 2, // f16 76 | 4, // f32 77 | 8, // f64 78 | 16, // f128 79 | 80 | // Special 81 | size, // trivial_wrapper 82 | sizeof(uintptr_t), // data_and_size, assume natural alignment 83 | }; 84 | 85 | const auto s = sizes[(uint32_t)id]; 86 | // Make sure we do not return an invalid value 87 | GAIA_ASSERT(s != (uint32_t)-1); 88 | return s; 89 | } 90 | 91 | template 92 | struct is_trivially_serializable { 93 | private: 94 | static constexpr bool update() { 95 | return std::is_enum_v || std::is_fundamental_v || std::is_trivially_copyable_v; 96 | } 97 | 98 | public: 99 | static constexpr bool value = update(); 100 | }; 101 | 102 | template 103 | struct is_int_kind_id: 104 | std::disjunction< 105 | std::is_same, std::is_same, // 106 | std::is_same, std::is_same, // 107 | std::is_same, std::is_same, // 108 | std::is_same, std::is_same, // 109 | std::is_same, std::is_same> {}; 110 | 111 | template 112 | struct is_flt_kind_id: 113 | std::disjunction< 114 | // std::is_same, // 115 | // std::is_same, // 116 | std::is_same, // 117 | std::is_same, // 118 | std::is_same> {}; 119 | 120 | template 121 | GAIA_NODISCARD constexpr serialization_type_id int_kind_id() { 122 | static_assert(is_int_kind_id::value, "Unsupported integral type"); 123 | 124 | if constexpr (std::is_same_v) { 125 | return serialization_type_id::s8; 126 | } else if constexpr (std::is_same_v) { 127 | return serialization_type_id::u8; 128 | } else if constexpr (std::is_same_v) { 129 | return serialization_type_id::s16; 130 | } else if constexpr (std::is_same_v) { 131 | return serialization_type_id::u16; 132 | } else if constexpr (std::is_same_v) { 133 | return serialization_type_id::s32; 134 | } else if constexpr (std::is_same_v) { 135 | return serialization_type_id::u32; 136 | } else if constexpr (std::is_same_v) { 137 | return serialization_type_id::s64; 138 | } else if constexpr (std::is_same_v) { 139 | return serialization_type_id::u64; 140 | } else if constexpr (std::is_same_v) { 141 | return serialization_type_id::u64; 142 | } else { // if constexpr (std::is_same_v) { 143 | return serialization_type_id::b; 144 | } 145 | } 146 | 147 | template 148 | GAIA_NODISCARD constexpr serialization_type_id flt_type_id() { 149 | static_assert(is_flt_kind_id::value, "Unsupported floating type"); 150 | 151 | // if constexpr (std::is_same_v) { 152 | // return serialization_type_id::f8; 153 | // } else if constexpr (std::is_same_v) { 154 | // return serialization_type_id::f16; 155 | // } else 156 | if constexpr (std::is_same_v) { 157 | return serialization_type_id::f32; 158 | } else if constexpr (std::is_same_v) { 159 | return serialization_type_id::f64; 160 | } else { // if constexpr (std::is_same_v) { 161 | return serialization_type_id::f128; 162 | } 163 | } 164 | 165 | template 166 | GAIA_NODISCARD constexpr serialization_type_id type_id() { 167 | if constexpr (std::is_enum_v) 168 | return int_kind_id>(); 169 | else if constexpr (std::is_integral_v) 170 | return int_kind_id(); 171 | else if constexpr (std::is_floating_point_v) 172 | return flt_type_id(); 173 | else if constexpr (core::has_size_begin_end::value) 174 | return serialization_type_id::data_and_size; 175 | else if constexpr (std::is_class_v) 176 | return serialization_type_id::trivial_wrapper; 177 | } 178 | 179 | // -------------------- 180 | // Define function detectors 181 | // -------------------- 182 | 183 | GAIA_DEFINE_HAS_MEMBER_FUNC(save); 184 | GAIA_DEFINE_HAS_MEMBER_FUNC(load); 185 | GAIA_DEFINE_HAS_MEMBER_FUNC(resize); 186 | 187 | // -------------------- 188 | // Customization tags 189 | // -------------------- 190 | 191 | struct save_tag {}; 192 | struct load_tag {}; 193 | inline constexpr save_tag save_v{}; 194 | inline constexpr load_tag load_v{}; 195 | 196 | // -------------------- 197 | // Detection traits 198 | // -------------------- 199 | 200 | template 201 | auto has_tag_save_impl(int) 202 | -> decltype(tag_invoke(save_v, std::declval(), std::declval()), std::true_type{}); 203 | template 204 | std::false_type has_tag_save_impl(...); 205 | template 206 | using has_tag_save = decltype(has_tag_save_impl(0)); 207 | 208 | template 209 | auto has_tag_load_impl(int) 210 | -> decltype(tag_invoke(load_v, std::declval(), std::declval()), std::true_type{}); 211 | template 212 | std::false_type has_tag_load_impl(...); 213 | template 214 | using has_tag_load = decltype(has_tag_load_impl(0)); 215 | } // namespace ser 216 | } // namespace gaia 217 | -------------------------------------------------------------------------------- /include/gaia/cnt/bitset_iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace gaia { 8 | namespace cnt { 9 | //! Bitset iterator 10 | //! \tparam TBitset Iterator's bitset parent 11 | //! \tparam IsFwd If true the iterator moves forward. Backwards iterations otherwise. 12 | //! \tparam IsInverse If true, all values are inverse 13 | template 14 | class bitset_const_iterator { 15 | public: 16 | using value_type = uint32_t; 17 | using size_type = typename TBitset::size_type; 18 | 19 | private: 20 | const TBitset* m_bitset = nullptr; 21 | value_type m_pos = 0; 22 | 23 | GAIA_NODISCARD size_type item(uint32_t wordIdx) const noexcept { 24 | if constexpr (IsInverse) 25 | return ~m_bitset->data(wordIdx); 26 | else 27 | return m_bitset->data(wordIdx); 28 | } 29 | 30 | GAIA_NODISCARD bool check_bit(uint32_t pos) const noexcept { 31 | if constexpr (IsInverse) 32 | return !m_bitset->test(pos); 33 | else 34 | return m_bitset->test(pos); 35 | } 36 | 37 | GAIA_NODISCARD uint32_t find_next_set_bit(uint32_t pos) const noexcept { 38 | value_type wordIndex = pos / TBitset::BitsPerItem; 39 | const auto item_count = m_bitset->items(); 40 | GAIA_ASSERT(wordIndex < item_count); 41 | size_type word = 0; 42 | 43 | const size_type posInWord = pos % TBitset::BitsPerItem + 1; 44 | if GAIA_LIKELY (posInWord < TBitset::BitsPerItem) { 45 | const size_type mask = (size_type(1) << posInWord) - 1; 46 | word = item(wordIndex) & (~mask); 47 | } 48 | 49 | GAIA_MSVC_WARNING_PUSH() 50 | GAIA_MSVC_WARNING_DISABLE(4244) 51 | while (true) { 52 | if (word != 0) { 53 | if constexpr (TBitset::BitsPerItem == 32) 54 | return wordIndex * TBitset::BitsPerItem + GAIA_FFS(word) - 1; 55 | else 56 | return wordIndex * TBitset::BitsPerItem + GAIA_FFS64(word) - 1; 57 | } 58 | 59 | // No set bit in the current word, move to the next one 60 | if (++wordIndex >= item_count) 61 | return pos; 62 | 63 | word = item(wordIndex); 64 | } 65 | GAIA_MSVC_WARNING_POP() 66 | } 67 | 68 | GAIA_NODISCARD uint32_t find_prev_set_bit(uint32_t pos) const noexcept { 69 | value_type wordIndex = pos / TBitset::BitsPerItem; 70 | GAIA_ASSERT(wordIndex < m_bitset->items()); 71 | 72 | const size_type posInWord = pos % TBitset::BitsPerItem; 73 | const size_type mask = (size_type(1) << posInWord) - 1; 74 | size_type word = item(wordIndex) & mask; 75 | 76 | GAIA_MSVC_WARNING_PUSH() 77 | GAIA_MSVC_WARNING_DISABLE(4244) 78 | while (true) { 79 | if (word != 0) { 80 | if constexpr (TBitset::BitsPerItem == 32) 81 | return TBitset::BitsPerItem * (wordIndex + 1) - GAIA_CTZ(word) - 1; 82 | else 83 | return TBitset::BitsPerItem * (wordIndex + 1) - GAIA_CTZ64(word) - 1; 84 | } 85 | 86 | // No set bit in the current word, move to the previous one 87 | if (wordIndex == 0) 88 | return pos; 89 | 90 | word = item(--wordIndex); 91 | } 92 | GAIA_MSVC_WARNING_POP() 93 | } 94 | 95 | public: 96 | bitset_const_iterator() = default; 97 | bitset_const_iterator(const TBitset& bitset, value_type pos, bool fwd): m_bitset(&bitset), m_pos(pos) { 98 | if (fwd) { 99 | if constexpr (!IsFwd) { 100 | // Find the first set bit 101 | if (pos != 0 || !check_bit(0)) { 102 | pos = find_next_set_bit(m_pos); 103 | // Point before the last item if no set bit was found 104 | if (pos == m_pos) 105 | pos = (value_type)-1; 106 | else 107 | --pos; 108 | } else 109 | --pos; 110 | } else { 111 | // Find the first set bit 112 | if (pos != 0 || !check_bit(0)) { 113 | pos = find_next_set_bit(m_pos); 114 | // Point beyond the last item if no set bit was found 115 | if (pos == m_pos) 116 | pos = bitset.size(); 117 | } 118 | } 119 | m_pos = pos; 120 | } else { 121 | const auto bitsetSize = bitset.size(); 122 | const auto lastBit = bitsetSize - 1; 123 | 124 | // Stay inside bounds 125 | if (pos >= bitsetSize) 126 | pos = bitsetSize - 1; 127 | 128 | if constexpr (!IsFwd) { 129 | // Find the last set bit 130 | if (pos != lastBit || !check_bit(pos)) { 131 | const auto newPos = find_prev_set_bit(pos); 132 | // Point one beyond the last found bit 133 | pos = (newPos == pos) ? bitsetSize - 1 : newPos; 134 | } 135 | } else { 136 | // Find the last set bit 137 | if (pos != lastBit || !check_bit(pos)) { 138 | const auto newPos = find_prev_set_bit(pos); 139 | // Point one beyond the last found bit 140 | pos = (newPos == pos) ? bitsetSize : newPos + 1; 141 | } 142 | // Point one beyond the last found bit 143 | else 144 | ++pos; 145 | } 146 | 147 | m_pos = pos; 148 | } 149 | } 150 | 151 | GAIA_NODISCARD value_type operator*() const { 152 | return m_pos; 153 | } 154 | 155 | GAIA_NODISCARD value_type operator->() const { 156 | return m_pos; 157 | } 158 | 159 | GAIA_NODISCARD value_type index() const { 160 | return m_pos; 161 | } 162 | 163 | bitset_const_iterator& operator++() { 164 | if constexpr (!IsFwd) { 165 | if (m_pos == (value_type)-1) 166 | return *this; 167 | 168 | auto newPos = find_prev_set_bit(m_pos); 169 | // Point one past the last item if no new bit was found 170 | if (newPos == m_pos) 171 | --newPos; 172 | m_pos = newPos; 173 | } else { 174 | auto newPos = find_next_set_bit(m_pos); 175 | // Point one past the last item if no new bit was found 176 | if (newPos == m_pos) 177 | ++newPos; 178 | m_pos = newPos; 179 | } 180 | 181 | return *this; 182 | } 183 | 184 | GAIA_NODISCARD bitset_const_iterator operator++(int) { 185 | bitset_const_iterator temp(*this); 186 | ++*this; 187 | return temp; 188 | } 189 | 190 | GAIA_NODISCARD bool operator==(const bitset_const_iterator& other) const { 191 | return m_pos == other.m_pos; 192 | } 193 | 194 | GAIA_NODISCARD bool operator!=(const bitset_const_iterator& other) const { 195 | return m_pos != other.m_pos; 196 | } 197 | }; 198 | 199 | template 200 | using const_iterator = bitset_const_iterator; 201 | template 202 | using const_iterator_inverse = bitset_const_iterator; 203 | template 204 | using const_reverse_iterator = bitset_const_iterator; 205 | template 206 | using const_reverse_inverse_iterator = bitset_const_iterator; 207 | } // namespace cnt 208 | } // namespace gaia -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | # Prevent building in-tree 5 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) 6 | message(FATAL_ERROR "Prevented in-tree built in ${CMAKE_SOURCE_DIR}. Create a 'build' directory outside of the source code and call cmake from there.") 7 | endif() 8 | 9 | set(GAIA_VERSION_REGEX "#define GAIA_VERSION_.*[ \t]+(.+)") 10 | file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/gaia/config/version.h" GAIA_VERSION REGEX ${GAIA_VERSION_REGEX}) 11 | list(TRANSFORM GAIA_VERSION REPLACE ${GAIA_VERSION_REGEX} "\\1") 12 | string(JOIN "." GAIA_VERSION ${GAIA_VERSION}) 13 | 14 | project( 15 | gaia 16 | VERSION ${GAIA_VERSION} 17 | DESCRIPTION "ECS framework" 18 | LANGUAGES CXX 19 | ) 20 | 21 | message(STATUS "*************************************************************") 22 | message(STATUS "${PROJECT_NAME}-ecs v${PROJECT_VERSION} (${CMAKE_BUILD_TYPE})") 23 | message(STATUS "Copyright (c) 2025 Richard Biely ") 24 | message(STATUS "*************************************************************") 25 | message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}") 26 | message(STATUS "CompPath: ${CMAKE_CXX_COMPILER}") 27 | message(STATUS "CompSim: ${CMAKE_CXX_SIMULATE_ID}") 28 | message(STATUS "CPU: ${CMAKE_SYSTEM_PROCESSOR}") 29 | message(STATUS "*************************************************************") 30 | 31 | include("${CMAKE_CURRENT_LIST_DIR}/pkg/cmake/sanitizers.cmake") 32 | 33 | # CPU 34 | string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" lower_processor) 35 | 36 | set(IsARM false) 37 | 38 | if("${lower_processor}" MATCHES "arm" OR "${lower_processor}" MATCHES "aarch") 39 | set(IsARM true) 40 | endif() 41 | 42 | # Compiler 43 | set(IsClangCompiler FALSE) 44 | 45 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 46 | set(IsClangCompiler TRUE) 47 | endif() 48 | 49 | # Configuration 50 | option(GAIA_USE_LIBCPP "Use libc++." OFF) 51 | 52 | if(IsClangCompiler) 53 | set(GAIA_USE_LIBCPP ON) 54 | endif() 55 | 56 | option(GAIA_BUILD_UNITTEST "Build unit test." OFF) 57 | option(GAIA_BUILD_BENCHMARK "Build benchmark." OFF) 58 | option(GAIA_BUILD_EXAMPLES "Build examples." OFF) 59 | option(GAIA_GENERATE_CC "Use ninja to generate compile_commands.json as a post-build step." OFF) 60 | option(GAIA_GENERATE_SINGLE_HEADER "Generate the single file header automatically." OFF) 61 | option(GAIA_GENERATE_PACKAGES "Generate packages" OFF) 62 | option(GAIA_GENERATE_DOCS "Build documentation" OFF) 63 | 64 | # Profiling settings 65 | option(GAIA_PROFILER_CPU "Enable CPU profiling." OFF) 66 | option(GAIA_PROFILER_MEM "Enable memory profiling." OFF) 67 | option(GAIA_PROFILER_BUILD "Build profiler if possible." OFF) 68 | 69 | # Library configuration 70 | option(GAIA_DEVMODE "Enables various verification checks. Only useful for library maintainers." OFF) 71 | option(GAIA_ECS_CHUNK_ALLOCATOR "If enabled, custom allocator is used for allocating archetype chunks." ON) 72 | option(GAIA_FORCE_DEBUG "If enabled, GAIA_DEBUG will be defined despite using the optimized build configuration." OFF) 73 | option(GAIA_DISABLE_ASSERTS "If enabled, no asserts will be thrown even in debug builds." OFF) 74 | 75 | # Special 76 | option(GAIA_MACOS_BUILD_HACK "If enabled, a special way to link executables is used to address linker issues. MacOS-specific. Ignored on other platform." OFF) 77 | 78 | if(GAIA_BUILD_UNITTEST) 79 | set(GAIA_BUILD_SRC ON) 80 | elseif(GAIA_BUILD_BENCHMARK) 81 | set(GAIA_BUILD_SRC ON) 82 | elseif(GAIA_BUILD_EXAMPLES) 83 | set(GAIA_BUILD_SRC ON) 84 | else() 85 | set(GAIA_BUILD_SRC OFF) 86 | endif() 87 | 88 | # libc++ 89 | if(NOT WIN32 AND GAIA_USE_LIBCPP) 90 | # Check for libc++ library presence 91 | find_library(LIBCXX_LIBRARY c++) 92 | 93 | if(LIBCXX_LIBRARY) 94 | include(CheckCXXSourceCompiles) 95 | include(CMakePushCheckState) 96 | 97 | cmake_push_check_state() 98 | set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -stdlib=libc++") 99 | 100 | check_cxx_source_compiles(" 101 | #include 102 | int main() { return std::is_same_v; } 103 | " GAIA_USE_LIBCPP) 104 | 105 | cmake_pop_check_state() 106 | endif() 107 | 108 | if(NOT GAIA_USE_LIBCPP) 109 | message(WARNING "GAIA_USE_LIBCPP is ON but libc++ is not available. The flag will be ignored.") 110 | endif() 111 | endif() 112 | 113 | # Documentation 114 | if(GAIA_GENERATE_DOCS) 115 | add_subdirectory(docs) 116 | endif() 117 | 118 | # Gaia target 119 | include(GNUInstallDirs) 120 | 121 | add_library(${PROJECT_NAME} INTERFACE) 122 | 123 | # Interface library 124 | target_include_directories( 125 | ${PROJECT_NAME} 126 | INTERFACE 127 | $ 128 | $ 129 | ) 130 | 131 | if(GAIA_HAS_LIBCPP) 132 | target_compile_options(${PROJECT_NAME} BEFORE INTERFACE -stdlib=libc++) 133 | endif() 134 | 135 | target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) 136 | 137 | # Install 138 | install(TARGETS ${PROJECT_NAME} 139 | EXPORT ${PROJECT_NAME}-targets 140 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 141 | 142 | install(DIRECTORY single_include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 143 | 144 | # Export targets 145 | install(EXPORT ${PROJECT_NAME}-targets 146 | FILE ${PROJECT_NAME}-targets.cmake 147 | NAMESPACE ${PROJECT_NAME}:: 148 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 149 | ) 150 | 151 | # Config & version files 152 | include(CMakePackageConfigHelpers) 153 | write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" 154 | VERSION ${PROJECT_VERSION} 155 | COMPATIBILITY SameMajorVersion 156 | ) 157 | 158 | configure_file( 159 | pkg/cmake/${PROJECT_NAME}Config.cmake.in 160 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" 161 | @ONLY 162 | ) 163 | 164 | install(FILES 165 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" 166 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" 167 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 168 | ) 169 | 170 | if(NOT GAIA_USE_SANITIZER STREQUAL "") 171 | add_definitions(-DGAIA_USE_SANITIZER=1) 172 | else() 173 | add_definitions(-DGAIA_USE_SANITIZER=0) 174 | endif() 175 | 176 | if(GAIA_GENERATE_PACKAGES) 177 | add_custom_target( 178 | conan_run 179 | COMMAND conan create ${CMAKE_SOURCE_DIR}/pkg/conan 180 | COMMENT "Conan: building + running test_package" 181 | USES_TERMINAL 182 | ) 183 | 184 | add_custom_target(default ALL DEPENDS conan_run) 185 | endif() 186 | 187 | if(GAIA_BUILD_SRC) 188 | add_subdirectory(src) 189 | endif() 190 | 191 | # # Print project variables 192 | # get_cmake_property(all_vars VARIABLES) 193 | 194 | # foreach(var ${all_vars}) 195 | # if(var MATCHES "^GAIA_") 196 | # message(STATUS "${var} = ${${var}}") 197 | # endif() 198 | # endforeach() 199 | -------------------------------------------------------------------------------- /include/gaia/core/hashing_policy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gaia/config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace gaia { 8 | namespace core { 9 | 10 | namespace detail { 11 | template 12 | struct is_direct_hash_key: std::false_type {}; 13 | template 14 | struct is_direct_hash_key>: std::true_type {}; 15 | 16 | //----------------------------------------------------------------------------------- 17 | 18 | constexpr void hash_combine2_out(uint32_t& lhs, uint32_t rhs) { 19 | lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2); 20 | } 21 | constexpr void hash_combine2_out(uint64_t& lhs, uint64_t rhs) { 22 | lhs ^= rhs + 0x9e3779B97f4a7c15ULL + (lhs << 6) + (lhs >> 2); 23 | } 24 | 25 | template 26 | GAIA_NODISCARD constexpr T hash_combine2(T lhs, T rhs) { 27 | hash_combine2_out(lhs, rhs); 28 | return lhs; 29 | } 30 | } // namespace detail 31 | 32 | template 33 | inline constexpr bool is_direct_hash_key_v = detail::is_direct_hash_key::value; 34 | 35 | template 36 | struct direct_hash_key { 37 | using Type = T; 38 | 39 | static_assert(std::is_integral_v); 40 | static constexpr bool IsDirectHashKey = true; 41 | 42 | T hash; 43 | bool operator==(direct_hash_key other) const { 44 | return hash == other.hash; 45 | } 46 | bool operator!=(direct_hash_key other) const { 47 | return hash != other.hash; 48 | } 49 | }; 50 | 51 | //! Combines values via OR. 52 | template 53 | constexpr auto combine_or([[maybe_unused]] T... t) { 54 | return (... | t); 55 | } 56 | 57 | //! Combines hashes into another complex one 58 | template 59 | constexpr T hash_combine(T first, T next, Rest... rest) { 60 | auto h = detail::hash_combine2(first, next); 61 | (detail::hash_combine2_out(h, rest), ...); 62 | return h; 63 | } 64 | 65 | #if GAIA_ECS_HASH == GAIA_ECS_HASH_FNV1A 66 | 67 | namespace detail { 68 | namespace fnv1a { 69 | constexpr uint64_t val_64_const = 0xcbf29ce484222325; 70 | constexpr uint64_t prime_64_const = 0x100000001b3; 71 | } // namespace fnv1a 72 | } // namespace detail 73 | 74 | constexpr uint64_t calculate_hash64(const char* const str) noexcept { 75 | uint64_t hash = detail::fnv1a::val_64_const; 76 | 77 | uint64_t i = 0; 78 | while (str[i] != '\0') { 79 | hash = (hash ^ uint64_t(str[i])) * detail::fnv1a::prime_64_const; 80 | ++i; 81 | } 82 | 83 | return hash; 84 | } 85 | 86 | constexpr uint64_t calculate_hash64(const char* const str, const uint64_t length) noexcept { 87 | uint64_t hash = detail::fnv1a::val_64_const; 88 | 89 | for (uint64_t i = 0; i < length; ++i) 90 | hash = (hash ^ uint64_t(str[i])) * detail::fnv1a::prime_64_const; 91 | 92 | return hash; 93 | } 94 | 95 | #elif GAIA_ECS_HASH == GAIA_ECS_HASH_MURMUR2A 96 | 97 | // Thank you https://gist.github.com/oteguro/10538695 98 | 99 | GAIA_MSVC_WARNING_PUSH() 100 | GAIA_MSVC_WARNING_DISABLE(4592) 101 | 102 | namespace detail { 103 | namespace murmur2a { 104 | constexpr uint64_t seed_64_const = 0xe17a1465ULL; 105 | constexpr uint64_t m = 0xc6a4a7935bd1e995ULL; 106 | constexpr uint64_t r = 47; 107 | 108 | constexpr uint64_t Load8(const char* data) { 109 | return (uint64_t(data[7]) << 56) | (uint64_t(data[6]) << 48) | (uint64_t(data[5]) << 40) | 110 | (uint64_t(data[4]) << 32) | (uint64_t(data[3]) << 24) | (uint64_t(data[2]) << 16) | 111 | (uint64_t(data[1]) << 8) | (uint64_t(data[0]) << 0); 112 | } 113 | 114 | constexpr uint64_t StaticHashValueLast64(uint64_t h) { 115 | return (((h * m) ^ ((h * m) >> r)) * m) ^ ((((h * m) ^ ((h * m) >> r)) * m) >> r); 116 | } 117 | 118 | constexpr uint64_t StaticHashValueLast64_(uint64_t h) { 119 | return (((h) ^ ((h) >> r)) * m) ^ ((((h) ^ ((h) >> r)) * m) >> r); 120 | } 121 | 122 | constexpr uint64_t StaticHashValue64Tail1(uint64_t h, const char* data) { 123 | return StaticHashValueLast64((h ^ uint64_t(data[0]))); 124 | } 125 | 126 | constexpr uint64_t StaticHashValue64Tail2(uint64_t h, const char* data) { 127 | return StaticHashValue64Tail1((h ^ uint64_t(data[1]) << 8), data); 128 | } 129 | 130 | constexpr uint64_t StaticHashValue64Tail3(uint64_t h, const char* data) { 131 | return StaticHashValue64Tail2((h ^ uint64_t(data[2]) << 16), data); 132 | } 133 | 134 | constexpr uint64_t StaticHashValue64Tail4(uint64_t h, const char* data) { 135 | return StaticHashValue64Tail3((h ^ uint64_t(data[3]) << 24), data); 136 | } 137 | 138 | constexpr uint64_t StaticHashValue64Tail5(uint64_t h, const char* data) { 139 | return StaticHashValue64Tail4((h ^ uint64_t(data[4]) << 32), data); 140 | } 141 | 142 | constexpr uint64_t StaticHashValue64Tail6(uint64_t h, const char* data) { 143 | return StaticHashValue64Tail5((h ^ uint64_t(data[5]) << 40), data); 144 | } 145 | 146 | constexpr uint64_t StaticHashValue64Tail7(uint64_t h, const char* data) { 147 | return StaticHashValue64Tail6((h ^ uint64_t(data[6]) << 48), data); 148 | } 149 | 150 | constexpr uint64_t StaticHashValueRest64(uint64_t h, uint64_t len, const char* data) { 151 | return ((len & 7) == 7) ? StaticHashValue64Tail7(h, data) 152 | : ((len & 7) == 6) ? StaticHashValue64Tail6(h, data) 153 | : ((len & 7) == 5) ? StaticHashValue64Tail5(h, data) 154 | : ((len & 7) == 4) ? StaticHashValue64Tail4(h, data) 155 | : ((len & 7) == 3) ? StaticHashValue64Tail3(h, data) 156 | : ((len & 7) == 2) ? StaticHashValue64Tail2(h, data) 157 | : ((len & 7) == 1) ? StaticHashValue64Tail1(h, data) 158 | : StaticHashValueLast64_(h); 159 | } 160 | 161 | constexpr uint64_t StaticHashValueLoop64(uint64_t i, uint64_t h, uint64_t len, const char* data) { 162 | return ( 163 | i == 0 ? StaticHashValueRest64(h, len, data) 164 | : StaticHashValueLoop64( 165 | i - 1, (h ^ (((Load8(data) * m) ^ ((Load8(data) * m) >> r)) * m)) * m, len, data + 8)); 166 | } 167 | 168 | constexpr uint64_t hash_murmur2a_64_ct(const char* key, uint64_t len, uint64_t seed) { 169 | return StaticHashValueLoop64(len / 8, seed ^ (len * m), (len), key); 170 | } 171 | } // namespace murmur2a 172 | } // namespace detail 173 | 174 | constexpr uint64_t calculate_hash64(uint64_t value) { 175 | value ^= value >> 33U; 176 | value *= 0xff51afd7ed558ccdULL; 177 | value ^= value >> 33U; 178 | 179 | value *= 0xc4ceb9fe1a85ec53ULL; 180 | value ^= value >> 33U; 181 | return value; 182 | } 183 | 184 | constexpr uint64_t calculate_hash64(const char* str) { 185 | uint64_t length = 0; 186 | while (str[length] != '\0') 187 | ++length; 188 | 189 | return detail::murmur2a::hash_murmur2a_64_ct(str, length, detail::murmur2a::seed_64_const); 190 | } 191 | 192 | constexpr uint64_t calculate_hash64(const char* str, uint64_t length) { 193 | return detail::murmur2a::hash_murmur2a_64_ct(str, length, detail::murmur2a::seed_64_const); 194 | } 195 | 196 | GAIA_MSVC_WARNING_POP() 197 | 198 | #else 199 | #error "Unknown hashing type defined" 200 | #endif 201 | 202 | } // namespace core 203 | } // namespace gaia 204 | --------------------------------------------------------------------------------