├── .clang-format ├── .clang-tidy ├── .clangd ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build.yml │ ├── coverage.yml │ └── sanitize.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── cmake ├── gaiaConfig.cmake.in └── sanitizers.cmake ├── docker ├── Dockerfile ├── amd64.Dockerfile ├── build.sh ├── build_clang.sh ├── build_clang_cachegrind.sh ├── build_gcc.sh ├── readme.md ├── setup.bat └── setup.sh ├── docs └── img │ ├── logo.png │ ├── logo_small.png │ ├── tracy_1.png │ └── tracy_2.png ├── gaia.code-workspace ├── include ├── gaia.h └── gaia │ ├── cnt │ ├── bitset.h │ ├── bitset_iterator.h │ ├── darray.h │ ├── darray_ext.h │ ├── dbitset.h │ ├── fwd_llist.h │ ├── ilist.h │ ├── impl │ │ ├── darray_ext_impl.h │ │ ├── darray_impl.h │ │ ├── sarray_ext_impl.h │ │ └── sarray_impl.h │ ├── map.h │ ├── paged_storage.h │ ├── sarray.h │ ├── sarray_ext.h │ ├── set.h │ ├── sparse_storage.h │ └── sringbuffer.h │ ├── config │ ├── config.h │ ├── config_core.h │ ├── config_core_end.h │ ├── logging.h │ ├── profiler.h │ └── version.h │ ├── core │ ├── bit_utils.h │ ├── dyn_singleton.h │ ├── func.h │ ├── hashing_policy.h │ ├── hashing_string.h │ ├── impl │ │ └── span_impl.h │ ├── iterator.h │ ├── span.h │ ├── string.h │ └── utility.h │ ├── ecs │ ├── api.h │ ├── api.inl │ ├── archetype.h │ ├── archetype_common.h │ ├── archetype_graph.h │ ├── chunk.h │ ├── chunk_allocator.h │ ├── chunk_header.h │ ├── chunk_iterator.h │ ├── command_buffer.h │ ├── command_buffer_fwd.h │ ├── common.h │ ├── component.h │ ├── component_cache.h │ ├── component_cache_item.h │ ├── component_desc.h │ ├── component_getter.h │ ├── component_setter.h │ ├── data_buffer.h │ ├── data_buffer_fwd.h │ ├── entity_container.h │ ├── id.h │ ├── id_fwd.h │ ├── query.h │ ├── query_cache.h │ ├── query_common.h │ ├── query_fwd.h │ ├── query_info.h │ ├── query_mask.h │ ├── system.inl │ ├── vm.h │ └── world.h │ ├── external │ ├── random.h │ └── robin_hood.h │ ├── mem │ ├── data_layout_policy.h │ ├── mem_alloc.h │ ├── mem_sani.h │ ├── mem_utils.h │ ├── paged_allocator.h │ ├── raw_data_holder.h │ └── stack_allocator.h │ ├── meta │ ├── reflection.h │ └── type_info.h │ ├── mt │ ├── event.h │ ├── futex.h │ ├── jobcommon.h │ ├── jobhandle.h │ ├── jobmanager.h │ ├── jobqueue.h │ ├── semaphore.h │ ├── semaphore_fast.h │ ├── spinlock.h │ └── threadpool.h │ ├── ser │ └── serialization.h │ └── util │ └── signal.h ├── make_single_header.bat ├── make_single_header.sh ├── single_include └── gaia.h └── src ├── CMakeLists.txt ├── examples ├── example1 │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── example2 │ ├── CMakeLists.txt │ └── src │ │ ├── dummy.cpp │ │ └── main.cpp ├── example3 │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── example_external │ ├── CMakeLists.txt │ ├── Makefile │ └── src │ │ └── main.cpp └── example_roguelike │ ├── CMakeLists.txt │ └── src │ └── main.cpp ├── perf ├── CMakeLists.txt ├── app │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── duel │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── entity │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── iter │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp └── mt │ ├── CMakeLists.txt │ └── src │ └── main.cpp └── test ├── CMakeLists.txt └── src └── main.cpp /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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] -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 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 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here such as hardware, operating system, compiler.... 28 | -------------------------------------------------------------------------------- /.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 | A clear and concise description of what the problem is.
12 | For example, `After adding a component to an entity I always get a crash`. 13 | 14 |

Describe the solution you'd like

15 | A clear and concise description of what you want to happen.
16 | For example, `I'm expecting that after deleting an entity no crash happens.` 17 | 18 |

Describe alternatives you've considered

19 | A clear and concise description of any alternative solutions or features you've considered.
20 | For example, `Rather than the program crashing silently, I'd expect an assert informing me that I did something that is not allowed.` 21 | 22 |

Additional context

23 | Add any other context or screenshots about the feature request here.
24 | 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.` 25 | -------------------------------------------------------------------------------- /.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 lcov 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 | CXXFLAGS: "--coverage -fno-elide-constructors -fno-inline -fno-default-inline -fprofile-update=atomic" 46 | CC: ccache gcc 47 | CXX: ccache g++ 48 | run: | 49 | cmake -DCMAKE_BUILD_TYPE=Debug -DGAIA_BUILD_UNITTEST=ON -DGAIA_BUILD_EXAMPLES=OFF -DGAIA_BUILD_BENCHMARK=OFF -DGAIA_GENERATE_CC=OFF -S . -B ${{github.workspace}}/build 50 | cmake --build ${{github.workspace}}/build --config Debug 51 | 52 | - name: Run tests 53 | working-directory: 54 | run: | 55 | ${{github.workspace}}/build/src/test/gaia_test 56 | 57 | - name: Collect data 58 | working-directory: ${{github.workspace}}/build/src/test 59 | run: | 60 | lcov -c -d . -o coverage.info --ignore-errors gcov,gcov,mismatch,mismatch 61 | lcov -l coverage.info 62 | 63 | - name: Upload coverage to Codecov 64 | uses: codecov/codecov-action@v5 65 | with: 66 | token: ${{secrets.CODECOV_TOKEN}} 67 | files: ${{github.workspace}}/build/src/test/coverage.info 68 | name: gaia-ecs 69 | fail_ci_if_error: true 70 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *build* 2 | ninja 3 | out 4 | cachegrind.* 5 | .vs 6 | .cache 7 | CMakeCache.txt 8 | CMakeSettings.json 9 | CMakeFiles 10 | .DS_Store -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "kr4is.cpptools-extension-pack", 4 | "ms-azuretools.vscode-docker" 5 | ] 6 | } -------------------------------------------------------------------------------- /.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 - example1", 21 | "type": "lldb", 22 | "request": "launch", 23 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example1/gaia_example1", 24 | "args": [], 25 | "cwd": "${workspaceFolder}", 26 | }, 27 | { 28 | "name": "(lldb) example - example2", 29 | "type": "lldb", 30 | "request": "launch", 31 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example2/gaia_example2", 32 | "args": [], 33 | "cwd": "${workspaceFolder}", 34 | }, 35 | { 36 | "name": "(lldb) example - example3", 37 | "type": "lldb", 38 | "request": "launch", 39 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example3/gaia_example3", 40 | "args": [], 41 | "cwd": "${workspaceFolder}", 42 | }, 43 | { 44 | "name": "(lldb) example - roguelike", 45 | "type": "lldb", 46 | "request": "launch", 47 | "program": "${workspaceFolder}/build/${input:buildType}/src/examples/example_roguelike/gaia_example_roguelike", 48 | "args": [], 49 | "cwd": "${workspaceFolder}", 50 | }, 51 | { 52 | "name": "(lldb) unit test", 53 | "type": "lldb", 54 | "request": "launch", 55 | "program": "${workspaceFolder}/build/${input:buildType}/src/test/gaia_test", 56 | "args": [], 57 | "cwd": "${workspaceFolder}", 58 | }, 59 | { 60 | "name": "(lldb) perf - duel", 61 | "type": "lldb", 62 | "request": "launch", 63 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/duel/gaia_perf_duel", 64 | "args": [], 65 | "cwd": "${workspaceFolder}", 66 | }, 67 | { 68 | "name": "(lldb) perf - iteration", 69 | "type": "lldb", 70 | "request": "launch", 71 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/iter/gaia_perf_iter", 72 | "args": [], 73 | "cwd": "${workspaceFolder}", 74 | }, 75 | { 76 | "name": "(lldb) perf - entity", 77 | "type": "lldb", 78 | "request": "launch", 79 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/entity/gaia_perf_entity", 80 | "args": [], 81 | "cwd": "${workspaceFolder}", 82 | }, 83 | { 84 | "name": "(lldb) perf - mt", 85 | "type": "lldb", 86 | "request": "launch", 87 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/mt/gaia_perf_mt", 88 | "args": [], 89 | "cwd": "${workspaceFolder}", 90 | }, 91 | { 92 | "name": "(lldb) perf - app", 93 | "type": "lldb", 94 | "request": "launch", 95 | "program": "${workspaceFolder}/build/${input:buildType}/src/perf/app/gaia_perf_app", 96 | "args": [], 97 | "cwd": "${workspaceFolder}", 98 | } 99 | ] 100 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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}/docker" 23 | }, 24 | "windows": { 25 | "command": "./setup.bat" 26 | }, 27 | "linux": { 28 | "command": "" 29 | }, 30 | "osx": { 31 | "command": "bash", 32 | "args": [ 33 | "setup.sh" 34 | ], 35 | }, 36 | "group": "build", 37 | "presentation": { 38 | "reveal": "always" 39 | }, 40 | "problemMatcher": [] 41 | }, 42 | { 43 | "label": "Profiler", 44 | "type": "shell", 45 | "command": "${workspaceFolder}/build/${input:buildType}/_deps/tracy-src/profiler/build/unix/tracy-profiler", 46 | "windows": { 47 | "command": "${workspaceFolder}/build/${input:buildType}/_deps/tracy-src/profiler/build/win32/tracy-profiler.exe", 48 | }, 49 | "group": "build", 50 | "presentation": { 51 | "reveal": "always" 52 | }, 53 | "problemMatcher": [] 54 | }, 55 | { 56 | "label": "Merge files into a single header", 57 | "type": "shell", 58 | "options": { 59 | "cwd": "${workspaceFolder}" 60 | }, 61 | "windows": { 62 | "command": "make_single_header.bat" 63 | }, 64 | "linux": { 65 | "command": "make_single_header.sh" 66 | }, 67 | "osx": { 68 | "command": "bash", 69 | "args": [ 70 | "make_single_header.sh" 71 | ], 72 | }, 73 | "group": "build", 74 | "presentation": { 75 | "reveal": "always" 76 | }, 77 | "problemMatcher": [] 78 | }, 79 | ] 80 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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@") -------------------------------------------------------------------------------- /cmake/sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | # use this file except in compliance with the License. You may obtain a copy of 6 | # the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations under 14 | # the License. 15 | 16 | include(CheckCXXSourceCompiles) 17 | 18 | set(GAIA_USE_SANITIZER 19 | "" 20 | CACHE 21 | STRING 22 | "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'" 23 | ) 24 | 25 | function(append value) 26 | foreach(variable ${ARGN}) 27 | set(${variable} 28 | "${${variable}} ${value}" 29 | PARENT_SCOPE) 30 | endforeach(variable) 31 | endfunction() 32 | 33 | function(test_san_flags return_var flags) 34 | set(QUIET_BACKUP ${CMAKE_REQUIRED_QUIET}) 35 | set(CMAKE_REQUIRED_QUIET TRUE) 36 | unset(${return_var} CACHE) 37 | set(FLAGS_BACKUP ${CMAKE_REQUIRED_FLAGS}) 38 | set(CMAKE_REQUIRED_FLAGS "${flags}") 39 | check_cxx_source_compiles("int main() { return 0; }" ${return_var}) 40 | set(CMAKE_REQUIRED_FLAGS "${FLAGS_BACKUP}") 41 | set(CMAKE_REQUIRED_QUIET "${QUIET_BACKUP}") 42 | endfunction() 43 | 44 | if(GAIA_USE_SANITIZER) 45 | append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 46 | 47 | unset(SANITIZER_SELECTED_FLAGS) 48 | 49 | if(UNIX) 50 | if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") 51 | append("-O1" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 52 | endif() 53 | 54 | if(GAIA_USE_SANITIZER MATCHES "([Aa]ddress)") 55 | # Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope 56 | message(STATUS "Testing with Address sanitizer") 57 | set(SANITIZER_ADDR_FLAG "-fsanitize=address") 58 | test_san_flags(SANITIZER_ADDR_AVAILABLE ${SANITIZER_ADDR_FLAG}) 59 | 60 | if(SANITIZER_ADDR_AVAILABLE) 61 | message(STATUS " Building with Address sanitizer") 62 | append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS) 63 | else() 64 | message(FATAL_ERROR "Address sanitizer not available for ${CMAKE_CXX_COMPILER}") 65 | endif() 66 | 67 | set(SANITIZER_ADDR_FLAG "-fsanitize=pointer-compare") 68 | 69 | if(SANITIZER_ADDR_AVAILABLE) 70 | message(STATUS " Building with pointer-compare sanitizer") 71 | append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS) 72 | else() 73 | message(FATAL_ERROR "pointer-compare sanitizer not available for ${CMAKE_CXX_COMPILER}") 74 | endif() 75 | 76 | set(SANITIZER_ADDR_FLAG "-fsanitize=pointer-subtract") 77 | 78 | if(SANITIZER_ADDR_AVAILABLE) 79 | message(STATUS " Building with pointer-subtract sanitizer") 80 | append("${SANITIZER_ADDR_FLAG}" SANITIZER_SELECTED_FLAGS) 81 | else() 82 | message(FATAL_ERROR "pointer-subtract sanitizer not available for ${CMAKE_CXX_COMPILER}") 83 | endif() 84 | endif() 85 | 86 | if(GAIA_USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)") 87 | # Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 88 | set(SANITIZER_MEM_FLAG "-fsanitize=memory") 89 | 90 | if(GAIA_USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") 91 | message(STATUS "Testing with MemoryWithOrigins sanitizer") 92 | append("-fsanitize-memory-track-origins" SANITIZER_MEM_FLAG) 93 | else() 94 | message(STATUS "Testing with Memory sanitizer") 95 | endif() 96 | 97 | test_san_flags(SANITIZER_MEM_AVAILABLE ${SANITIZER_MEM_FLAG}) 98 | 99 | if(SANITIZER_MEM_AVAILABLE) 100 | if(GAIA_USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") 101 | message(STATUS " Building with MemoryWithOrigins sanitizer") 102 | else() 103 | message(STATUS " Building with Memory sanitizer") 104 | endif() 105 | 106 | append("${SANITIZER_MEM_FLAG}" SANITIZER_SELECTED_FLAGS) 107 | else() 108 | message(FATAL_ERROR "Memory [With Origins] sanitizer not available for ${CMAKE_CXX_COMPILER}") 109 | endif() 110 | endif() 111 | 112 | if(GAIA_USE_SANITIZER MATCHES "([Uu]ndefined)") 113 | message(STATUS "Testing with Undefined Behaviour sanitizer") 114 | set(SANITIZER_UB_FLAG "-fsanitize=undefined") 115 | 116 | if(EXISTS "${BLACKLIST_FILE}") 117 | append("-fsanitize-blacklist=${BLACKLIST_FILE}" SANITIZER_UB_FLAG) 118 | endif() 119 | 120 | test_san_flags(SANITIZER_UB_AVAILABLE ${SANITIZER_UB_FLAG}) 121 | 122 | if(SANITIZER_UB_AVAILABLE) 123 | message(STATUS " Building with Undefined Behaviour sanitizer") 124 | append("${SANITIZER_UB_FLAG}" SANITIZER_SELECTED_FLAGS) 125 | else() 126 | message(FATAL_ERROR "Undefined Behaviour sanitizer not available for ${CMAKE_CXX_COMPILER}") 127 | endif() 128 | endif() 129 | 130 | if(GAIA_USE_SANITIZER MATCHES "([Tt]hread)") 131 | message(STATUS "Testing with Thread sanitizer") 132 | set(SANITIZER_THREAD_FLAG "-fsanitize=thread") 133 | test_san_flags(SANITIZER_THREAD_AVAILABLE ${SANITIZER_THREAD_FLAG}) 134 | 135 | if(SANITIZER_THREAD_AVAILABLE) 136 | message(STATUS " Building with Thread sanitizer") 137 | append("${SANITIZER_THREAD_FLAG}" SANITIZER_SELECTED_FLAGS) 138 | else() 139 | message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}") 140 | endif() 141 | endif() 142 | 143 | if(GAIA_USE_SANITIZER MATCHES "([Ll]eak)") 144 | message(STATUS "Testing with Leak sanitizer") 145 | set(SANITIZER_LEAK_FLAG "-fsanitize=leak") 146 | test_san_flags(SANITIZER_LEAK_AVAILABLE ${SANITIZER_LEAK_FLAG}) 147 | 148 | if(SANITIZER_LEAK_AVAILABLE) 149 | message(STATUS " Building with Leak sanitizer") 150 | append("${SANITIZER_LEAK_FLAG}" SANITIZER_SELECTED_FLAGS) 151 | else() 152 | message(FATAL_ERROR "Thread sanitizer not available for ${CMAKE_CXX_COMPILER}") 153 | endif() 154 | endif() 155 | 156 | message(STATUS "Sanitizer flags: ${SANITIZER_SELECTED_FLAGS}") 157 | test_san_flags(SANITIZER_SELECTED_COMPATIBLE ${SANITIZER_SELECTED_FLAGS}) 158 | 159 | if(SANITIZER_SELECTED_COMPATIBLE) 160 | message(STATUS " Building with ${SANITIZER_SELECTED_FLAGS}") 161 | append("${SANITIZER_SELECTED_FLAGS}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 162 | else() 163 | message(FATAL_ERROR " Sanitizer flags ${SANITIZER_SELECTED_FLAGS} are not compatible.") 164 | endif() 165 | elseif(MSVC) 166 | if(GAIA_USE_SANITIZER MATCHES "([Aa]ddress)") 167 | message(STATUS "Building with Address sanitizer") 168 | append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 169 | else() 170 | message( 171 | FATAL_ERROR 172 | "This sanitizer not yet supported in the MSVC environment: ${GAIA_USE_SANITIZER}" 173 | ) 174 | endif() 175 | else() 176 | message(FATAL_ERROR "GAIA_USE_SANITIZER is not supported on this platform.") 177 | endif() 178 | endif() -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/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 | -------------------------------------------------------------------------------- /docker/build_clang.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 | 24 | #################################################################### 25 | # Build the project 26 | #################################################################### 27 | 28 | # Build parameters 29 | BUILD_SETTINGS_COMMON_BASE="-DGAIA_BUILD_BENCHMARK=ON -DGAIA_BUILD_EXAMPLES=ON -DGAIA_GENERATE_CC=OFF -DGAIA_MAKE_SINGLE_HEADER=OFF -DGAIA_PROFILER_BUILD=OFF" 30 | BUILD_SETTINGS_COMMON="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_BUILD_UNITTEST=ON -DGAIA_PROFILER_CPU=OFF -DGAIA_PROFILER_MEM=OFF" 31 | BUILD_SETTINGS_COMMON_PROF="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_BUILD_UNITTEST=OFF -DGAIA_PROFILER_CPU=ON -DGAIA_PROFILER_MEM=ON" 32 | # For sanitizer builds we have to turn off unit tests because Catch2 generates unitialized memory alerts. 33 | # These are false alerts happening due to us not linking against a standard library built with memory sanitizers enabled. 34 | # TODO: Build custom libc++ with msan enabled 35 | BUILD_SETTINGS_COMMON_SANI="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_BUILD_UNITTEST=OFF -DGAIA_PROFILER_CPU=OFF -DGAIA_PROFILER_MEM=OFF -DGAIA_ECS_CHUNK_ALLOCATOR=OFF" 36 | 37 | # Paths 38 | PATH_DEBUG="./${PATH_BASE}/debug" 39 | PATH_DEBUG_SYSA="./${PATH_BASE}/debug-sysa" 40 | PATH_DEBUG_PROF="${PATH_DEBUG}-prof" 41 | PATH_RELEASE="./${PATH_BASE}/release" 42 | PATH_DEBUG_ADDR="${PATH_DEBUG}-addr" 43 | PATH_DEBUG_MEM="${PATH_DEBUG}-mem" 44 | PATH_RELEASE_ADDR="${PATH_RELEASE}-addr" 45 | PATH_RELEASE_MEM="${PATH_RELEASE}-mem" 46 | 47 | # Sanitizer settings 48 | SANI_ADDR="'Address;Undefined'" 49 | SANI_MEM="'MemoryWithOrigins'" 50 | 51 | # Debug mode 52 | cmake -E make_directory ${PATH_DEBUG} 53 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON} -DGAIA_DEVMODE=ON -S .. -B ${PATH_DEBUG} 54 | if ! cmake --build ${PATH_DEBUG} --config Debug; then 55 | echo "${PATH_DEBUG} build failed" 56 | exit 1 57 | fi 58 | 59 | # Debug mode + system allocator 60 | cmake -E make_directory ${PATH_DEBUG_SYSA} 61 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON} -DGAIA_DEVMODE=ON -DGAIA_ECS_CHUNK_ALLOCATOR=OFF -S .. -B ${PATH_DEBUG_SYSA} 62 | if ! cmake --build ${PATH_DEBUG_SYSA} --config Debug; then 63 | echo "${PATH_DEBUG_SYSA} build failed" 64 | exit 1 65 | fi 66 | 67 | # Debug mode + profiler 68 | cmake -E make_directory ${PATH_DEBUG_PROF} 69 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON_PROF} -DGAIA_DEVMODE=ON -S .. -B ${PATH_DEBUG_PROF} 70 | if ! cmake --build ${PATH_DEBUG_PROF} --config Debug; then 71 | echo "${PATH_DEBUG_PROF} build failed" 72 | exit 1 73 | fi 74 | 75 | # Release mode 76 | cmake -E make_directory ${PATH_RELEASE} 77 | cmake -DCMAKE_BUILD_TYPE=Release ${BUILD_SETTINGS_COMMON} -S .. -B ${PATH_RELEASE} 78 | if ! cmake --build ${PATH_RELEASE} --config Release; then 79 | echo "${PATH_RELEASE} build failed" 80 | exit 1 81 | fi 82 | 83 | # Debug mode - address sanitizers 84 | cmake -E make_directory ${PATH_DEBUG_ADDR} 85 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON_SANI} -DGAIA_USE_SANITIZER=${SANI_ADDR} -S .. -B ${PATH_DEBUG_ADDR} 86 | if ! cmake --build ${PATH_DEBUG_ADDR} --config Debug; then 87 | echo "${PATH_DEBUG_ADDR} build failed" 88 | exit 1 89 | fi 90 | 91 | # Debug mode - memory sanitizers 92 | cmake -E make_directory ${PATH_DEBUG_MEM} 93 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON_SANI} -DGAIA_USE_SANITIZER=${SANI_MEM} -S .. -B ${PATH_DEBUG_MEM} 94 | if ! cmake --build ${PATH_DEBUG_MEM} --config Debug; then 95 | echo "${PATH_DEBUG_MEM} build failed" 96 | exit 1 97 | fi 98 | 99 | # Release mode - address sanitizers 100 | cmake -E make_directory ${PATH_RELEASE_ADDR} 101 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ${BUILD_SETTINGS_COMMON_SANI} -DGAIA_USE_SANITIZER=${SANI_ADDR} -S .. -B ${PATH_RELEASE_ADDR} 102 | if ! cmake --build ${PATH_RELEASE_ADDR} --config RelWithDebInfo; then 103 | echo "${PATH_RELEASE_ADDR} build failed" 104 | exit 1 105 | fi 106 | 107 | # Release mode - memory sanitizers 108 | cmake -E make_directory ${PATH_RELEASE_MEM} 109 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ${BUILD_SETTINGS_COMMON_SANI} -DGAIA_USE_SANITIZER=${SANI_MEM} -S .. -B ${PATH_RELEASE_MEM} 110 | if ! cmake --build ${PATH_RELEASE_MEM} --config RelWithDebInfo; then 111 | echo "${PATH_RELEASE_MEM} build failed" 112 | exit 1 113 | fi 114 | 115 | #################################################################### 116 | # Run analysis 117 | #################################################################### 118 | 119 | PERF_ENTITY_PATH="src/perf/entity/gaia_perf_entity" 120 | PERF_ITER_PATH="src/perf/iter/gaia_perf_iter" 121 | PERF_DUEL_PATH="src/perf/duel/gaia_perf_duel" 122 | PERF_APP_PATH="src/perf/app/gaia_perf_app" 123 | PERF_MT_PATH="src/perf/mt/gaia_perf_mt" 124 | 125 | echo ${PATH_DEBUG_ADDR}/${PERF_ENTITY_PATH} 126 | echo "Debug mode + addr sanitizer" 127 | chmod +x ${PATH_DEBUG_ADDR}/${PERF_ENTITY_PATH} 128 | ${PATH_DEBUG_ADDR}/${PERF_ENTITY_PATH} -s 129 | echo "Debug mode + mem sanitizer" 130 | chmod +x ${PATH_DEBUG_MEM}/${PERF_ENTITY_PATH} 131 | ${PATH_DEBUG_MEM}/${PERF_ENTITY_PATH} -s 132 | echo "Release mode + addr sanitizer" 133 | chmod +x ${PATH_RELEASE_ADDR}/${PERF_ENTITY_PATH} 134 | ${PATH_RELEASE_ADDR}/${PERF_ENTITY_PATH} -s 135 | echo "Release mode + mem sanitizer" 136 | chmod +x ${PATH_RELEASE_MEM}/${PERF_ENTITY_PATH} 137 | ${PATH_RELEASE_MEM}/${PERF_ENTITY_PATH} -s 138 | 139 | echo ${PATH_DEBUG_ADDR}/${PERF_ITER_PATH} 140 | echo "Debug mode + addr sanitizer" 141 | chmod +x ${PATH_DEBUG_ADDR}/${PERF_ITER_PATH} 142 | ${PATH_DEBUG_ADDR}/${PERF_ITER_PATH} -s 143 | echo "Debug mode + mem sanitizer" 144 | chmod +x ${PATH_DEBUG_MEM}/${PERF_ITER_PATH} 145 | ${PATH_DEBUG_MEM}/${PERF_ITER_PATH} -s 146 | echo "Release mode + addr sanitizer" 147 | chmod +x ${PATH_RELEASE_ADDR}/${PERF_ITER_PATH} 148 | ${PATH_RELEASE_ADDR}/${PERF_ITER_PATH} -s 149 | echo "Release mode + mem sanitizer" 150 | chmod +x ${PATH_RELEASE_MEM}/${PERF_ITER_PATH} 151 | ${PATH_RELEASE_MEM}/${PERF_ITER_PATH} -s 152 | 153 | echo ${PATH_DEBUG_ADDR}/${PERF_DUEL_PATH} 154 | echo "Debug mode + addr sanitizer" 155 | chmod +x ${PATH_DEBUG_ADDR}/${PERF_DUEL_PATH} 156 | ${PATH_DEBUG_ADDR}/${PERF_DUEL_PATH} -s 157 | echo "Debug mode + mem sanitizer" 158 | chmod +x ${PATH_DEBUG_MEM}/${PERF_DUEL_PATH} 159 | ${PATH_DEBUG_MEM}/${PERF_DUEL_PATH} -s 160 | echo "Release mode + addr sanitizer" 161 | chmod +x ${PATH_RELEASE_ADDR}/${PERF_DUEL_PATH} 162 | ${PATH_RELEASE_ADDR}/${PERF_DUEL_PATH} -s 163 | echo "Release mode + mem sanitizer" 164 | chmod +x ${PATH_RELEASE_MEM}/${PERF_DUEL_PATH} 165 | ${PATH_RELEASE_MEM}/${PERF_DUEL_PATH} -s 166 | 167 | echo ${PATH_DEBUG_ADDR}/${PERF_APP_PATH} 168 | echo "Debug mode + addr sanitizer" 169 | chmod +x ${PATH_DEBUG_ADDR}/${PERF_APP_PATH} 170 | ${PATH_DEBUG_ADDR}/${PERF_APP_PATH} -s 171 | echo "Debug mode + mem sanitizer" 172 | chmod +x ${PATH_DEBUG_MEM}/${PERF_APP_PATH} 173 | ${PATH_DEBUG_MEM}/${PERF_APP_PATH} -s 174 | echo "Release mode + addr sanitizer" 175 | chmod +x ${PATH_RELEASE_ADDR}/${PERF_APP_PATH} 176 | ${PATH_RELEASE_ADDR}/${PERF_APP_PATH} -s 177 | echo "Release mode + mem sanitizer" 178 | chmod +x ${PATH_RELEASE_MEM}/${PERF_APP_PATH} 179 | ${PATH_RELEASE_MEM}/${PERF_APP_PATH} -s 180 | 181 | echo ${PATH_DEBUG_ADDR}/${PERF_MT_PATH} 182 | echo "Debug mode + addr sanitizer" 183 | chmod +x ${PATH_DEBUG_ADDR}/${PERF_MT_PATH} 184 | ${PATH_DEBUG_ADDR}/${PERF_MT_PATH} -s 185 | echo "Debug mode + mem sanitizer" 186 | chmod +x ${PATH_DEBUG_MEM}/${PERF_MT_PATH} 187 | ${PATH_DEBUG_MEM}/${PERF_MT_PATH} -s 188 | echo "Release mode + addr sanitizer" 189 | chmod +x ${PATH_RELEASE_ADDR}/${PERF_MT_PATH} 190 | ${PATH_RELEASE_ADDR}/${PERF_MT_PATH} -s 191 | echo "Release mode + mem sanitizer" 192 | chmod +x ${PATH_RELEASE_MEM}/${PERF_MT_PATH} 193 | ${PATH_RELEASE_MEM}/${PERF_MT_PATH} -s -------------------------------------------------------------------------------- /docker/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 | 24 | #################################################################### 25 | # Build the project 26 | #################################################################### 27 | 28 | 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" 29 | BUILD_SETTINGS_COMMON="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_PROFILER_CPU=OFF -DGAIA_PROFILER_MEM=OFF" 30 | PATH_RELEASE="./${PATH_BASE}/release-cachegrind" 31 | 32 | # Release mode 33 | cmake -E make_directory ${PATH_RELEASE} 34 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ${BUILD_SETTINGS_COMMON} -DGAIA_DEBUG=0 -S .. -B ${PATH_RELEASE} 35 | if ! cmake --build ${PATH_RELEASE} --config RelWithDebInfo; then 36 | echo "${PATH_DEBUG} build failed" 37 | exit 1 38 | fi 39 | 40 | #################################################################### 41 | # Run cachegrind 42 | #################################################################### 43 | 44 | OUTPUT_BASE="src/perf/duel/gaia_perf_duel" 45 | OUTPUT_ARGS_DOD="-p -dod" 46 | OUTPUT_ARGS_ECS="-p" 47 | 48 | VALGRIND_ARGS="--tool=cachegrind" 49 | 50 | # Debug mode 51 | chmod +x ${PATH_RELEASE}/${OUTPUT_BASE} 52 | 53 | # We need to adjust how cachegrind is called based on what CPU we have. 54 | CURRENT_PLATFORM=$(uname -p) 55 | if [[ "$CURRENT_PLATFORM" = "i386|i686|x86_64" ]]; then 56 | # Most Intel a AMD CPUs should work just fine using a generic cachegrind call 57 | VALGRIND_ARGS_CUSTOM="" 58 | else 59 | # If we are not an x86 CPU we will assume an ARM. Namely Apple M1. 60 | # Docker at least up to version 4.17 is somewhat broken for ARM CPUs and does not propagate /proc/cpuinfo to the virtual machine. 61 | # 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. 62 | # However, virtually noone besides the maintainers is going to use this tool so we take the incorrect but good-enough-for-now way 63 | # and will assume an Apple M1. 64 | 65 | # M1 chips are not detected properly so we have to force cache sizes. 66 | # M1 has both performance and efficiency cores which differ in their setup: 67 | # performance cores: I1=192kiB 8-way, D1=131kiB 8-way, L2=12MiB 16-way 68 | # efficiency cores : I1=128kiB 8-way, D1=64kiB 8-way, L2=4MiB 16-way 69 | # Unfortunatelly, Cachegrind thinks the cache can only be a power of 2 in size. Therefore, performance cores can't be measured 70 | # properly and we have to simulate at least the efficiency cores. 71 | 72 | # M1 performance core (won't run because L1 cache is not a power of 2): 73 | # VALGRIND_ARGS_CUSTOM="--I1=196608,8,128 --D1=131072,8,128 --L2=12582912,16,128 --cache-sim=yes" 74 | # M1 efficiency core: 75 | VALGRIND_ARGS_CUSTOM="--I1=131072,8,128 --D1=65536,8,128 --L2=4194304,16,128 --cache-sim=yes" 76 | fi 77 | 78 | echo "Cachegrind - measuring DOD performance" 79 | valgrind ${VALGRIND_ARGS} ${VALGRIND_ARGS_CUSTOM} --cachegrind-out-file=cachegrind.out.dod --branch-sim=yes "${PATH_RELEASE}/${OUTPUT_BASE}" ${OUTPUT_ARGS_DOD} 80 | cg_annotate --show=Dr,D1mr,DLmr --sort=Dr,D1mr,DLmr cachegrind.out.dod > cachegrind.r.dod # cache reads 81 | cg_annotate --show=Dw,D1mw,DLmw --sort=Dw,D1mw,DLmw cachegrind.out.dod > cachegrind.w.dod # cache writes 82 | cg_annotate --show=Bc,Bcm,Bi,Bim --sort=Bc,Bcm,Bi,Bim cachegrind.out.dod > cachegrind.b.dod # branch hits 83 | 84 | echo "Cachegrind - measuring ECS performance" 85 | valgrind ${VALGRIND_ARGS} ${VALGRIND_ARGS_CUSTOM} --cachegrind-out-file=cachegrind.out.ecs --branch-sim=yes "${PATH_RELEASE}/${OUTPUT_BASE}" ${OUTPUT_ARGS_ECS} 86 | cg_annotate --show=Dr,D1mr,DLmr --sort=Dr,D1mr,DLmr cachegrind.out.ecs > cachegrind.r.ecs 87 | cg_annotate --show=Dw,D1mw,DLmw --sort=Dw,D1mw,DLmw cachegrind.out.ecs > cachegrind.w.ecs 88 | cg_annotate --show=Bc,Bcm,Bi,Bim --sort=Bc,Bcm,Bi,Bim cachegrind.out.ecs > cachegrind.b.ecs 89 | -------------------------------------------------------------------------------- /docker/build_gcc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH_BASE="build-gcc" 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/gcc" 22 | export CXX="/usr/bin/g++" 23 | 24 | #################################################################### 25 | # Build the project 26 | #################################################################### 27 | 28 | BUILD_SETTINGS_COMMON_BASE="-DGAIA_BUILD_BENCHMARK=ON -DGAIA_BUILD_EXAMPLES=ON -DGAIA_GENERATE_CC=OFF -DGAIA_MAKE_SINGLE_HEADER=OFF -DGAIA_PROFILER_BUILD=OFF" 29 | BUILD_SETTINGS_COMMON="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_BUILD_UNITTEST=ON -DGAIA_PROFILER_CPU=OFF -DGAIA_PROFILER_MEM=OFF" 30 | BUILD_SETTINGS_COMMON_PROF="${BUILD_SETTINGS_COMMON_BASE} -DGAIA_BUILD_UNITTEST=OFF -DGAIA_PROFILER_CPU=ON -DGAIA_PROFILER_MEM=ON" 31 | PATH_DEBUG="./${PATH_BASE}/debug" 32 | PATH_DEBUG_PROF="${PATH_DEBUG}-prof" 33 | PATH_RELEASE="./${PATH_BASE}/release" 34 | 35 | # Debug mode 36 | cmake -E make_directory ${PATH_DEBUG} 37 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON} -DGAIA_DEVMODE=ON -S .. -B ${PATH_DEBUG} 38 | if ! cmake --build ${PATH_DEBUG} --config Debug; then 39 | echo "${PATH_DEBUG} build failed" 40 | exit 1 41 | fi 42 | 43 | # Debug mode + profiler 44 | cmake -E make_directory ${PATH_DEBUG_PROF} 45 | cmake -DCMAKE_BUILD_TYPE=Debug ${BUILD_SETTINGS_COMMON_PROF} -DGAIA_DEVMODE=ON -S .. -B ${PATH_DEBUG_PROF} 46 | if ! cmake --build ${PATH_DEBUG_PROF} --config Debug; then 47 | echo "${PATH_DEBUG_PROF} build failed" 48 | exit 1 49 | fi 50 | 51 | # Release mode 52 | cmake -E make_directory ${PATH_RELEASE} 53 | cmake -DCMAKE_BUILD_TYPE=Release ${BUILD_SETTINGS_COMMON} -S .. -B ${PATH_RELEASE} 54 | if ! cmake --build ${PATH_RELEASE} --config Release; then 55 | echo "${PATH_RELEASE} build failed" 56 | exit 1 57 | fi 58 | 59 | #################################################################### 60 | # Run unit tests 61 | #################################################################### 62 | 63 | UNIT_TEST_PATH="src/test/gaia_test" 64 | 65 | # Debug mode 66 | chmod +x ${PATH_DEBUG}/${UNIT_TEST_PATH} 67 | ${PATH_DEBUG}/${UNIT_TEST_PATH} 68 | 69 | # Release mode 70 | chmod +x ${PATH_RELEASE}/${UNIT_TEST_PATH} 71 | ${PATH_RELEASE}/${UNIT_TEST_PATH} -------------------------------------------------------------------------------- /docker/readme.md: -------------------------------------------------------------------------------- 1 | To prepare a docker container inside the "docker" folder run: 2 | ```bash 3 | bash ./setup.sh 4 | ``` 5 | 6 | If bash if your default shell it is enough to run: 7 | ```bash 8 | ./setup.sh 9 | ``` 10 | 11 | Once done, you can build the project using both Clang and GCC by running: 12 | ```bash 13 | ./build.sh 14 | ``` 15 | 16 | For Clang-only build run: 17 | ```bash 18 | ./build_clang.sh 19 | ``` 20 | 21 | For GCC-only build run: 22 | ```bash 23 | ./build_gcc.sh 24 | ``` 25 | -------------------------------------------------------------------------------- /docker/setup.bat: -------------------------------------------------------------------------------- 1 | echo off 2 | 3 | set imagename=gaiaecs-linux-builder 4 | set imagename_tmp=%imagename%-tmp 5 | 6 | @REM Stop running containers on the image and remove them 7 | docker stop -t 0 %imagename% 8 | 9 | @REM Make sure the container is removed 10 | docker rm %imagename% 11 | 12 | @REM Build the image if necessary. Decide what docker file to use based on the detected platform 13 | set currach=%PROCESSOR_ARCHITECTURE% 14 | 15 | if "%currach%"=="ARM64" ( 16 | @REM Generic docker file 17 | docker build --file Dockerfile --tag %imagename% . 18 | ) else if "%currach%"=="AMD64" ( 19 | @REM Use specialized version for amd64 because the Intel compiler is not compatible with anything else 20 | docker build --file amd64.Dockerfile --tag %imagename% . 21 | ) else if "%currach%"=="x86" ( 22 | @REM Use specialized version for amd64 because the Intel compiler is not compatible with anything else 23 | docker build --file amd64.Dockerfile --tag %imagename% . 24 | ) else ( 25 | echo The current platform architecture is unknown: %currach% 26 | exit 0 27 | ) 28 | 29 | @REM Start the container 30 | docker volume create %imagename_tmp% 31 | set currdir=%~dp0 32 | docker run -p 2022:22 --rm --interactive --tty --privileged ^ 33 | --name %imagename% ^ 34 | --mount type=volume,source=%imagename_tmp%,target=/work-output ^ 35 | --mount type=bind,source=%currdir%..,target=/gaia-ecs ^ 36 | --workdir /gaia-ecs/docker %imagename% bash 37 | 38 | pause -------------------------------------------------------------------------------- /docker/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | imagename="gaiaecs-linux-builder" 4 | imagename_tmp=${imagename}"tmp" 5 | 6 | # Stop running containers on the image and remove them 7 | docker stop -t 0 ${imagename} 8 | 9 | # Make sure the container is removed 10 | docker rm ${imagename} 11 | 12 | # Build the image if necessary. Decide what docker file to use based on the detected platform 13 | CURRENT_PLATFORM=$(uname -p) 14 | if [[ "$CURRENT_PLATFORM" = "i386|i686|x86_64" ]]; then 15 | # Use specialized version for amd64 because the Intel compiler is not compatible with anything else 16 | docker build --file amd64.Dockerfile --tag ${imagename} . 17 | else 18 | # Generic docker file 19 | docker build --file Dockerfile --tag ${imagename} . 20 | fi 21 | 22 | # Start the container 23 | docker volume create ${imagename_tmp} 24 | docker run -p 2022:22 --rm --interactive --tty --privileged --name ${imagename} --mount type=volume,source=${imagename_tmp},target=/work-output --mount type=bind,source=$(pwd)/..,target=/gaia-ecs --workdir /gaia-ecs/docker ${imagename} bash 25 | -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/03c77b4de90e231b25ca9f9aae203d3d9101cb41/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/03c77b4de90e231b25ca9f9aae203d3d9101cb41/docs/img/logo_small.png -------------------------------------------------------------------------------- /docs/img/tracy_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/03c77b4de90e231b25ca9f9aae203d3d9101cb41/docs/img/tracy_1.png -------------------------------------------------------------------------------- /docs/img/tracy_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardbiely/gaia-ecs/03c77b4de90e231b25ca9f9aae203d3d9101cb41/docs/img/tracy_2.png -------------------------------------------------------------------------------- /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.h: -------------------------------------------------------------------------------- 1 | #include "gaia/config/config.h" 2 | #include "gaia/config/logging.h" 3 | #include "gaia/config/profiler.h" 4 | 5 | #include "gaia/core/bit_utils.h" 6 | #include "gaia/core/hashing_policy.h" 7 | #include "gaia/core/iterator.h" 8 | #include "gaia/core/span.h" 9 | #include "gaia/core/utility.h" 10 | 11 | #include "gaia/meta/reflection.h" 12 | #include "gaia/meta/type_info.h" 13 | 14 | #include "gaia/mem/data_layout_policy.h" 15 | #include "gaia/mem/mem_alloc.h" 16 | #include "gaia/mem/mem_sani.h" 17 | #include "gaia/mem/mem_utils.h" 18 | #include "gaia/mem/raw_data_holder.h" 19 | #include "gaia/mem/stack_allocator.h" 20 | 21 | #include "gaia/cnt/bitset.h" 22 | #include "gaia/cnt/bitset_iterator.h" 23 | #include "gaia/cnt/darray.h" 24 | #include "gaia/cnt/darray_ext.h" 25 | #include "gaia/cnt/dbitset.h" 26 | #include "gaia/cnt/fwd_llist.h" 27 | #include "gaia/cnt/ilist.h" 28 | #include "gaia/cnt/map.h" 29 | #include "gaia/cnt/paged_storage.h" 30 | #include "gaia/cnt/sarray.h" 31 | #include "gaia/cnt/sarray_ext.h" 32 | #include "gaia/cnt/set.h" 33 | #include "gaia/cnt/sparse_storage.h" 34 | #include "gaia/cnt/sringbuffer.h" 35 | 36 | #include "gaia/util/signal.h" 37 | 38 | #include "gaia/ser/serialization.h" 39 | 40 | #include "gaia/mt/threadpool.h" 41 | 42 | #include "gaia/ecs/archetype.h" 43 | #include "gaia/ecs/chunk.h" 44 | #include "gaia/ecs/chunk_allocator.h" 45 | #include "gaia/ecs/chunk_iterator.h" 46 | #include "gaia/ecs/command_buffer.h" 47 | #include "gaia/ecs/common.h" 48 | #include "gaia/ecs/component.h" 49 | #include "gaia/ecs/component_getter.h" 50 | #include "gaia/ecs/component_setter.h" 51 | #include "gaia/ecs/id.h" 52 | #include "gaia/ecs/query.h" 53 | #include "gaia/ecs/world.h" 54 | -------------------------------------------------------------------------------- /include/gaia/cnt/bitset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "bitset_iterator.h" 8 | 9 | namespace gaia { 10 | namespace cnt { 11 | template 12 | class bitset { 13 | public: 14 | static constexpr uint32_t BitCount = NBits; 15 | static_assert(NBits > 0); 16 | 17 | private: 18 | template 19 | struct size_type_selector { 20 | using type = std::conditional_t; 21 | }; 22 | 23 | public: 24 | static constexpr uint32_t BitsPerItem = (NBits / 64) > 0 ? 64 : 32; 25 | static constexpr uint32_t Items = (NBits + BitsPerItem - 1) / BitsPerItem; 26 | 27 | using size_type = typename size_type_selector::type; 28 | 29 | private: 30 | static constexpr bool HasTrailingBits = (NBits % BitsPerItem) != 0; 31 | static constexpr size_type LastItemMask = ((size_type)1 << (NBits % BitsPerItem)) - 1; 32 | 33 | size_type m_data[Items]{}; 34 | 35 | //! Returns the word stored at the index \param wordIdx 36 | size_type data(uint32_t wordIdx) const { 37 | return m_data[wordIdx]; 38 | } 39 | 40 | public: 41 | using this_bitset = bitset; 42 | using iter = const_iterator; 43 | using iter_inv = const_iterator_inverse; 44 | using iter_rev = const_reverse_iterator; 45 | using iter_rev_inv = const_reverse_inverse_iterator; 46 | friend iter; 47 | friend iter_inv; 48 | friend iter_rev; 49 | friend iter_rev_inv; 50 | 51 | constexpr size_type* data() { 52 | return &m_data[0]; 53 | } 54 | 55 | constexpr const size_type* data() const { 56 | return &m_data[0]; 57 | } 58 | 59 | //! Returns the number of words used by the bitset internally 60 | GAIA_NODISCARD constexpr uint32_t items() const { 61 | return Items; 62 | } 63 | 64 | constexpr iter begin() const { 65 | return iter(*this, 0, true); 66 | } 67 | 68 | constexpr iter end() const { 69 | return iter(*this, NBits, false); 70 | } 71 | 72 | constexpr iter_rev rbegin() const { 73 | return iter_rev(*this, NBits, false); 74 | } 75 | 76 | constexpr iter_rev rend() const { 77 | return iter_rev(*this, 0, true); 78 | } 79 | 80 | constexpr iter_inv ibegin() const { 81 | return iter_inv(*this, 0, true); 82 | } 83 | 84 | constexpr iter_inv iend() const { 85 | return iter_inv(*this, NBits, false); 86 | } 87 | 88 | constexpr iter_rev_inv ribegin() const { 89 | return iter_rev_inv(*this, NBits, false); 90 | } 91 | 92 | constexpr iter_rev_inv riend() const { 93 | return iter_rev_inv(*this, 0, true); 94 | } 95 | 96 | GAIA_NODISCARD constexpr bool operator[](uint32_t pos) const { 97 | return test(pos); 98 | } 99 | 100 | GAIA_NODISCARD constexpr bool operator==(const bitset& other) const { 101 | GAIA_FOR(Items) { 102 | if (m_data[i] != other.m_data[i]) 103 | return false; 104 | } 105 | return true; 106 | } 107 | 108 | GAIA_NODISCARD constexpr bool operator!=(const bitset& other) const { 109 | GAIA_FOR(Items) { 110 | if (m_data[i] == other.m_data[i]) 111 | return false; 112 | } 113 | return true; 114 | } 115 | 116 | //! Sets all bits 117 | constexpr void set() { 118 | if constexpr (HasTrailingBits) { 119 | GAIA_FOR(Items - 1) m_data[i] = (size_type)-1; 120 | m_data[Items - 1] = LastItemMask; 121 | } else { 122 | GAIA_FOR(Items) m_data[i] = (size_type)-1; 123 | } 124 | } 125 | 126 | //! Sets the bit at the postion \param pos to value \param value 127 | constexpr void set(uint32_t pos, bool value = true) { 128 | GAIA_ASSERT(pos < NBits); 129 | if (value) 130 | m_data[pos / BitsPerItem] |= ((size_type)1 << (pos % BitsPerItem)); 131 | else 132 | m_data[pos / BitsPerItem] &= ~((size_type)1 << (pos % BitsPerItem)); 133 | } 134 | 135 | //! Flips all bits 136 | constexpr bitset& flip() { 137 | if constexpr (HasTrailingBits) { 138 | GAIA_FOR(Items - 1) m_data[i] = ~m_data[i]; 139 | m_data[Items - 1] = (~m_data[Items - 1]) & LastItemMask; 140 | } else { 141 | GAIA_FOR(Items) m_data[i] = ~m_data[i]; 142 | } 143 | return *this; 144 | } 145 | 146 | //! Flips the bit at the postion \param pos 147 | constexpr void flip(uint32_t pos) { 148 | GAIA_ASSERT(pos < NBits); 149 | const auto wordIdx = pos / BitsPerItem; 150 | const auto bitIdx = pos % BitsPerItem; 151 | m_data[wordIdx] ^= ((size_type)1 << bitIdx); 152 | } 153 | 154 | //! Flips all bits from \param bitFrom to \param bitTo (including) 155 | constexpr bitset& flip(uint32_t bitFrom, uint32_t bitTo) { 156 | GAIA_ASSERT(bitFrom <= bitTo); 157 | GAIA_ASSERT(bitTo < size()); 158 | 159 | // The following can't happen because we always have at least 1 bit 160 | // if GAIA_UNLIKELY (size() == 0) 161 | // return *this; 162 | 163 | const uint32_t wordIdxFrom = bitFrom / BitsPerItem; 164 | const uint32_t wordIdxTo = bitTo / BitsPerItem; 165 | 166 | auto getMask = [](uint32_t from, uint32_t to) -> size_type { 167 | const auto diff = to - from; 168 | // Set all bits when asking for the full range 169 | if (diff == BitsPerItem - 1) 170 | return (size_type)-1; 171 | 172 | return ((size_type(1) << (diff + 1)) - 1) << from; 173 | }; 174 | 175 | if (wordIdxFrom == wordIdxTo) { 176 | m_data[wordIdxTo] ^= getMask(bitFrom % BitsPerItem, bitTo % BitsPerItem); 177 | } else { 178 | // First word 179 | m_data[wordIdxFrom] ^= getMask(bitFrom % BitsPerItem, BitsPerItem - 1); 180 | // Middle 181 | GAIA_FOR2(wordIdxFrom + 1, wordIdxTo) m_data[i] = ~m_data[i]; 182 | // Last word 183 | m_data[wordIdxTo] ^= getMask(0, bitTo % BitsPerItem); 184 | } 185 | 186 | return *this; 187 | } 188 | 189 | //! Unsets all bits 190 | constexpr void reset() { 191 | GAIA_FOR(Items) m_data[i] = 0; 192 | } 193 | 194 | //! Unsets the bit at the postion \param pos 195 | constexpr void reset(uint32_t pos) { 196 | GAIA_ASSERT(pos < NBits); 197 | m_data[pos / BitsPerItem] &= ~((size_type)1 << (pos % BitsPerItem)); 198 | } 199 | 200 | //! Returns the value of the bit at the position \param pos 201 | GAIA_NODISCARD constexpr bool test(uint32_t pos) const { 202 | GAIA_ASSERT(pos < NBits); 203 | return (m_data[pos / BitsPerItem] & ((size_type)1 << (pos % BitsPerItem))) != 0; 204 | } 205 | 206 | //! Checks if all bits are set 207 | GAIA_NODISCARD constexpr bool all() const { 208 | if constexpr (HasTrailingBits) { 209 | GAIA_FOR(Items - 1) { 210 | if (m_data[i] != (size_type)-1) 211 | return false; 212 | } 213 | return (m_data[Items - 1] & LastItemMask) == LastItemMask; 214 | } else { 215 | GAIA_FOR(Items) { 216 | if (m_data[i] != (size_type)-1) 217 | return false; 218 | } 219 | return true; 220 | } 221 | } 222 | 223 | //! Checks if any bit is set 224 | GAIA_NODISCARD constexpr bool any() const { 225 | GAIA_FOR(Items) { 226 | if (m_data[i] != 0) 227 | return true; 228 | } 229 | return false; 230 | } 231 | 232 | //! Checks if all bits are reset 233 | GAIA_NODISCARD constexpr bool none() const { 234 | GAIA_FOR(Items) { 235 | if (m_data[i] != 0) 236 | return false; 237 | } 238 | return true; 239 | } 240 | 241 | //! Returns the number of set bits 242 | GAIA_NODISCARD uint32_t count() const { 243 | uint32_t total = 0; 244 | 245 | GAIA_MSVC_WARNING_PUSH() 246 | GAIA_MSVC_WARNING_DISABLE(4244) 247 | if constexpr (sizeof(size_type) == 4) { 248 | GAIA_FOR(Items) total += GAIA_POPCNT(m_data[i]); 249 | } else { 250 | GAIA_FOR(Items) total += GAIA_POPCNT64(m_data[i]); 251 | } 252 | GAIA_MSVC_WARNING_POP() 253 | 254 | return total; 255 | } 256 | 257 | //! Returns the number of bits the bitset can hold 258 | GAIA_NODISCARD constexpr uint32_t size() const { 259 | return NBits; 260 | } 261 | }; 262 | } // namespace cnt 263 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/cnt/bitset_iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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 -------------------------------------------------------------------------------- /include/gaia/cnt/darray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "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 | -------------------------------------------------------------------------------- /include/gaia/cnt/darray_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "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/fwd_llist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include "../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 81 | //! the allocated nodes. 82 | //! Inserts: O(1) 83 | //! Removals: O(1) 84 | //! Iteration: O(N) 85 | template 86 | struct fwd_llist { 87 | uint32_t count = 0; 88 | T* first = nullptr; 89 | 90 | //! Clears the list. 91 | void clear() { 92 | count = 0; 93 | first = nullptr; 94 | } 95 | 96 | //! Links the node in the list. 97 | void link(T* pNode) { 98 | GAIA_ASSERT(pNode != nullptr); 99 | 100 | auto& link = pNode->get_fwd_llist_link(); 101 | link.next = first; 102 | if (first != nullptr) { 103 | auto& linkFirst = first->get_fwd_llist_link(); 104 | linkFirst.prevs_next = &(link.next); 105 | first = pNode; 106 | } 107 | link.prevs_next = &first; 108 | first = pNode; 109 | 110 | ++count; 111 | } 112 | 113 | //! Unlinks the node from the list. 114 | void unlink(T* pNode) { 115 | GAIA_ASSERT(pNode != nullptr); 116 | 117 | auto& link = pNode->get_fwd_llist_link(); 118 | *(link.prevs_next) = link.next; 119 | if (link.next != nullptr) { 120 | auto& linkNext = link.next->get_fwd_llist_link(); 121 | linkNext.prevs_next = link.prevs_next; 122 | } 123 | 124 | // Reset the node's link 125 | link = {}; 126 | 127 | --count; 128 | } 129 | 130 | //! Checks if the node \param pNode is linked in the list. 131 | GAIA_NODISCARD bool has(T* pNode) const { 132 | GAIA_ASSERT(pNode != nullptr); 133 | 134 | for (auto& curr: *this) { 135 | if (&curr == pNode) 136 | return true; 137 | } 138 | 139 | return false; 140 | } 141 | 142 | //! Returns true if the list is empty. False otherwise. 143 | GAIA_NODISCARD bool empty() const { 144 | GAIA_ASSERT(count == 0); 145 | return first == nullptr; 146 | } 147 | 148 | //! Returns the number of nodes linked in the list. 149 | GAIA_NODISCARD uint32_t size() const { 150 | return count; 151 | } 152 | 153 | fwd_llist_iterator begin() { 154 | return fwd_llist_iterator(first); 155 | } 156 | 157 | fwd_llist_iterator end() { 158 | return fwd_llist_iterator(nullptr); 159 | } 160 | 161 | fwd_llist_iterator begin() const { 162 | return fwd_llist_iterator((const T*)first); 163 | } 164 | 165 | fwd_llist_iterator end() const { 166 | return fwd_llist_iterator((const T*)nullptr); 167 | } 168 | }; 169 | } // namespace cnt 170 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/cnt/ilist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "../core/utility.h" 8 | #include "darray.h" 9 | 10 | namespace gaia { 11 | namespace cnt { 12 | struct ilist_item_base {}; 13 | 14 | struct ilist_item: public ilist_item_base { 15 | //! Allocated items: Index in the list. 16 | //! Deleted items: Index of the next deleted item in the list. 17 | uint32_t idx; 18 | //! Generation ID 19 | uint32_t gen; 20 | 21 | ilist_item() = default; 22 | ilist_item(uint32_t index, uint32_t generation): idx(index), gen(generation) {} 23 | 24 | ilist_item(const ilist_item& other) { 25 | idx = other.idx; 26 | gen = other.gen; 27 | } 28 | ilist_item& operator=(const ilist_item& other) { 29 | GAIA_ASSERT(core::addressof(other) != this); 30 | idx = other.idx; 31 | gen = other.gen; 32 | return *this; 33 | } 34 | 35 | ilist_item(ilist_item&& other) { 36 | idx = other.idx; 37 | gen = other.gen; 38 | 39 | other.idx = (uint32_t)-1; 40 | other.gen = (uint32_t)-1; 41 | } 42 | ilist_item& operator=(ilist_item&& other) { 43 | GAIA_ASSERT(core::addressof(other) != this); 44 | idx = other.idx; 45 | gen = other.gen; 46 | 47 | other.idx = (uint32_t)-1; 48 | other.gen = (uint32_t)-1; 49 | return *this; 50 | } 51 | }; 52 | 53 | template 54 | class darray_ilist_storage: public cnt::darray { 55 | public: 56 | void add_item(TListItem&& container) { 57 | this->push_back(GAIA_MOV(container)); 58 | } 59 | 60 | void del_item([[maybe_unused]] TListItem& container) {} 61 | }; 62 | 63 | //! Implicit list. Rather than with pointers, items \tparam TListItem are linked 64 | //! together through an internal indexing mechanism. To the outside world they are 65 | //! presented as \tparam TItemHandle. All items are stored in a container instance 66 | //! of the type \tparam TInternalStorage. 67 | //! \tparam TListItem needs to have idx and gen variables and expose a constructor 68 | //! that initializes them. 69 | template > 70 | struct ilist { 71 | using internal_storage = TInternalStorage; 72 | // TODO: replace this iterator with a real list iterator 73 | using iterator = typename internal_storage::iterator; 74 | 75 | using iterator_category = typename internal_storage::iterator_category; 76 | using value_type = TListItem; 77 | using reference = TListItem&; 78 | using const_reference = const TListItem&; 79 | using pointer = TListItem*; 80 | using const_pointer = TListItem*; 81 | using difference_type = typename internal_storage::difference_type; 82 | using size_type = typename internal_storage::size_type; 83 | 84 | static_assert(std::is_base_of::value); 85 | //! Implicit list items 86 | internal_storage m_items; 87 | //! Index of the next item to recycle 88 | size_type m_nextFreeIdx = (size_type)-1; 89 | //! Number of items to recycle 90 | size_type m_freeItems = 0; 91 | 92 | GAIA_NODISCARD pointer data() noexcept { 93 | return (pointer)m_items.data(); 94 | } 95 | 96 | GAIA_NODISCARD const_pointer data() const noexcept { 97 | return (const_pointer)m_items.data(); 98 | } 99 | 100 | GAIA_NODISCARD reference operator[](size_type index) { 101 | return m_items[index]; 102 | } 103 | GAIA_NODISCARD const_reference operator[](size_type index) const { 104 | return m_items[index]; 105 | } 106 | 107 | void clear() { 108 | m_items.clear(); 109 | m_nextFreeIdx = (size_type)-1; 110 | m_freeItems = 0; 111 | } 112 | 113 | GAIA_NODISCARD size_type get_next_free_item() const noexcept { 114 | return m_nextFreeIdx; 115 | } 116 | 117 | GAIA_NODISCARD size_type get_free_items() const noexcept { 118 | return m_freeItems; 119 | } 120 | 121 | GAIA_NODISCARD size_type item_count() const noexcept { 122 | return size() - m_freeItems; 123 | } 124 | 125 | GAIA_NODISCARD size_type size() const noexcept { 126 | return (size_type)m_items.size(); 127 | } 128 | 129 | GAIA_NODISCARD bool empty() const noexcept { 130 | return size() == 0; 131 | } 132 | 133 | GAIA_NODISCARD size_type capacity() const noexcept { 134 | return (size_type)m_items.capacity(); 135 | } 136 | 137 | GAIA_NODISCARD iterator begin() const noexcept { 138 | return m_items.begin(); 139 | } 140 | 141 | GAIA_NODISCARD iterator end() const noexcept { 142 | return m_items.end(); 143 | } 144 | 145 | void reserve(size_type cap) { 146 | m_items.reserve(cap); 147 | } 148 | 149 | //! Allocates a new item in the list 150 | //! \return Handle to the new item 151 | GAIA_NODISCARD TItemHandle alloc(void* ctx) { 152 | if GAIA_UNLIKELY (m_freeItems == 0U) { 153 | // We don't want to go out of range for new item 154 | const auto itemCnt = (size_type)m_items.size(); 155 | GAIA_ASSERT(itemCnt < TItemHandle::IdMask && "Trying to allocate too many items!"); 156 | 157 | GAIA_GCC_WARNING_PUSH() 158 | GAIA_CLANG_WARNING_PUSH() 159 | GAIA_GCC_WARNING_DISABLE("-Wstringop-overflow"); 160 | GAIA_GCC_WARNING_DISABLE("-Wmissing-field-initializers"); 161 | GAIA_CLANG_WARNING_DISABLE("-Wmissing-field-initializers"); 162 | m_items.add_item(TListItem::create(itemCnt, 0U, ctx)); 163 | return TListItem::handle(m_items.back()); 164 | GAIA_GCC_WARNING_POP() 165 | GAIA_CLANG_WARNING_POP() 166 | } 167 | 168 | // Make sure the list is not broken 169 | GAIA_ASSERT(m_nextFreeIdx < (size_type)m_items.size() && "Item recycle list broken!"); 170 | 171 | --m_freeItems; 172 | const auto index = m_nextFreeIdx; 173 | auto& j = m_items[m_nextFreeIdx]; 174 | m_nextFreeIdx = j.idx; 175 | j = TListItem::create(index, j.gen, ctx); 176 | return TListItem::handle(j); 177 | } 178 | 179 | //! Allocates a new item in the list 180 | //! \return Handle to the new item 181 | GAIA_NODISCARD TItemHandle alloc() { 182 | if GAIA_UNLIKELY (m_freeItems == 0U) { 183 | // We don't want to go out of range for new item 184 | const auto itemCnt = (size_type)m_items.size(); 185 | GAIA_ASSERT(itemCnt < TItemHandle::IdMask && "Trying to allocate too many items!"); 186 | 187 | GAIA_GCC_WARNING_PUSH() 188 | GAIA_CLANG_WARNING_PUSH() 189 | GAIA_GCC_WARNING_DISABLE("-Wstringop-overflow"); 190 | GAIA_GCC_WARNING_DISABLE("-Wmissing-field-initializers"); 191 | GAIA_CLANG_WARNING_DISABLE("-Wmissing-field-initializers"); 192 | m_items.add_item(TListItem(itemCnt, 0U)); 193 | return {itemCnt, 0U}; 194 | GAIA_GCC_WARNING_POP() 195 | GAIA_CLANG_WARNING_POP() 196 | } 197 | 198 | // Make sure the list is not broken 199 | GAIA_ASSERT(m_nextFreeIdx < (size_type)m_items.size() && "Item recycle list broken!"); 200 | 201 | --m_freeItems; 202 | const auto index = m_nextFreeIdx; 203 | auto& j = m_items[m_nextFreeIdx]; 204 | m_nextFreeIdx = j.idx; 205 | return {index, m_items[index].gen}; 206 | } 207 | 208 | //! Invalidates \param handle. 209 | //! Every time an item is deallocated its generation is increased by one. 210 | TListItem& free(TItemHandle handle) { 211 | auto& item = m_items[handle.id()]; 212 | m_items.del_item(item); 213 | 214 | // Update our implicit list 215 | if GAIA_UNLIKELY (m_freeItems == 0) 216 | item.idx = TItemHandle::IdMask; 217 | else 218 | item.idx = m_nextFreeIdx; 219 | ++item.gen; 220 | 221 | m_nextFreeIdx = handle.id(); 222 | ++m_freeItems; 223 | 224 | return item; 225 | } 226 | 227 | //! Verifies that the implicit linked list is valid 228 | void validate() const { 229 | bool hasThingsToRemove = m_freeItems > 0; 230 | if (!hasThingsToRemove) 231 | return; 232 | 233 | // If there's something to remove there has to be at least one entity left 234 | GAIA_ASSERT(!m_items.empty()); 235 | 236 | auto freeItems = m_freeItems; 237 | auto nextFreeItem = m_nextFreeIdx; 238 | while (freeItems > 0) { 239 | GAIA_ASSERT(nextFreeItem < m_items.size() && "Item recycle list broken!"); 240 | 241 | nextFreeItem = m_items[nextFreeItem].idx; 242 | --freeItems; 243 | } 244 | 245 | // At this point the index of the last item in list should 246 | // point to -1 because that's the tail of our implicit list. 247 | GAIA_ASSERT(nextFreeItem == TItemHandle::IdMask); 248 | } 249 | }; 250 | } // namespace cnt 251 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/cnt/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../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/sarray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "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/sarray_ext.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "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/set.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../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 | -------------------------------------------------------------------------------- /include/gaia/config/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config_core.h" 3 | #include "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_SET_HOOKS 58 | #define GAIA_ENABLE_SET_HOOKS (GAIA_ENABLE_HOOKS && 1) 59 | #else 60 | // If GAIA_ENABLE_SET_HOOKS is defined and GAIA_ENABLE_HOOKS is not, unset it 61 | #ifndef GAIA_ENABLE_HOOKS 62 | #undef GAIA_ENABLE_SET_HOOKS 63 | #define GAIA_ENABLE_SET_HOOKS 0 64 | #endif 65 | #endif 66 | 67 | //! If enabled, reference counting of entities is enabled. This gives you access to ecs::SafeEntity 68 | //! and similar features. 69 | #ifndef GAIA_USE_SAFE_ENTITY 70 | #define GAIA_USE_SAFE_ENTITY 1 71 | #endif 72 | 73 | //! If enabled, reference counting of entities is enabled. This gives you access to ecs::SafeEntity 74 | //! and similar features. 75 | #ifndef GAIA_USE_WEAK_ENTITY 76 | #define GAIA_USE_WEAK_ENTITY 1 77 | #endif 78 | 79 | //! If enabled, various API supporting variadic template arguments is made available. 80 | //! More comfortable to use, but compilation time may increase. 81 | #ifndef GAIA_USE_VARIADIC_API 82 | #define GAIA_USE_VARIADIC_API 0 83 | #endif 84 | 85 | //! If enabled, a bloom filter is used for speed up matching of archetypes in queries. 86 | //! If disabled, no filter is applied. 87 | //! Possible values: 88 | //! -1 - No filter 89 | //! 0 - Bloom filter (calculates a 8 byte hash for each archetype) 90 | //! 1 - Partitioned bloom filter (calculates 4x8 byte hash for each archetype) 91 | //! Partitioned bloom filter is slightly more computationaly expensive but gives less false postives. 92 | //! Therefore, it will be more useful when there is a lot of archetypes with very different components. 93 | #ifndef GAIA_USE_PARTITIONED_BLOOM_FILTER 94 | #define GAIA_USE_PARTITIONED_BLOOM_FILTER 1 95 | #endif 96 | 97 | //------------------------------------------------------------------------------ 98 | 99 | #include "config_core_end.h" 100 | -------------------------------------------------------------------------------- /include/gaia/config/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // vsnprintf, sscanf, printf 4 | 5 | //! Log - debug 6 | #ifndef GAIA_LOG_D 7 | #define GAIA_LOG_D(...) \ 8 | { \ 9 | fprintf(stdout, "\033[1;32m%s %s, D: ", __DATE__, __TIME__); \ 10 | fprintf(stdout, __VA_ARGS__); \ 11 | fprintf(stdout, "\033[0m\n"); \ 12 | } 13 | #endif 14 | 15 | //! Log - normal/informational 16 | #ifndef GAIA_LOG_N 17 | #define GAIA_LOG_N(...) \ 18 | { \ 19 | fprintf(stdout, "%s %s, I: ", __DATE__, __TIME__); \ 20 | fprintf(stdout, __VA_ARGS__); \ 21 | fprintf(stdout, "\n"); \ 22 | } 23 | #endif 24 | 25 | //! Log - warning 26 | #ifndef GAIA_LOG_W 27 | #define GAIA_LOG_W(...) \ 28 | { \ 29 | fprintf(stderr, "\033[1;33m%s %s, W: ", __DATE__, __TIME__); \ 30 | fprintf(stderr, __VA_ARGS__); \ 31 | fprintf(stderr, "\033[0m\n"); \ 32 | } 33 | #endif 34 | 35 | //! Log - error 36 | #ifndef GAIA_LOG_E 37 | #define GAIA_LOG_E(...) \ 38 | { \ 39 | fprintf(stderr, "\033[1;31m%s %s, E: ", __DATE__, __TIME__); \ 40 | fprintf(stderr, __VA_ARGS__); \ 41 | fprintf(stderr, "\033[0m\n"); \ 42 | } 43 | #endif -------------------------------------------------------------------------------- /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/core/bit_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "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 -------------------------------------------------------------------------------- /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 "../config/config.h" 3 | #include 4 | 5 | namespace gaia { 6 | namespace detail { 7 | template 8 | struct is_reference_wrapper: std::false_type {}; 9 | template 10 | struct is_reference_wrapper>: std::true_type {}; 11 | 12 | template 13 | constexpr decltype(auto) invoke_memptr(Pointed C::* member, Object&& object, Args&&... args) { 14 | using object_t = std::decay_t; 15 | constexpr bool is_member_function = std::is_function_v; 16 | constexpr bool is_wrapped = is_reference_wrapper::value; 17 | constexpr bool is_derived_object = std::is_same_v || std::is_base_of_v; 18 | 19 | if constexpr (is_member_function) { 20 | if constexpr (is_derived_object) 21 | return (GAIA_FWD(object).*member)(GAIA_FWD(args)...); 22 | else if constexpr (is_wrapped) 23 | return (object.get().*member)(GAIA_FWD(args)...); 24 | else 25 | return ((*GAIA_FWD(object)).*member)(GAIA_FWD(args)...); 26 | } else { 27 | static_assert(std::is_object_v && sizeof...(args) == 0); 28 | if constexpr (is_derived_object) 29 | return GAIA_FWD(object).*member; 30 | else if constexpr (is_wrapped) 31 | return object.get().*member; 32 | else 33 | return (*GAIA_FWD(object)).*member; 34 | } 35 | } 36 | } // namespace detail 37 | 38 | template 39 | constexpr decltype(auto) 40 | 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/core/hashing_policy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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 | -------------------------------------------------------------------------------- /include/gaia/core/hashing_string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "hashing_policy.h" 7 | #include "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 | //! Constructor calculating hash and length from the provided string \param pStr 43 | //! \warning String has to be null-terminated and up to MaxLen characters long. 44 | //! Undefined behavior otherwise. 45 | explicit StringLookupKey(const char* pStr): 46 | m_pStr(pStr), m_len(len(pStr)), m_owned(0), m_hash(calc(pStr, m_len)) {} 47 | //! Constructor calculating hash from the provided string \param pStr and \param length 48 | //! \warning String has to be null-terminated and up to MaxLen characters long. 49 | //! Undefined behavior otherwise. 50 | explicit StringLookupKey(const char* pStr, uint32_t len): 51 | m_pStr(pStr), m_len(len), m_owned(0), m_hash(calc(pStr, len)) {} 52 | //! Constructor just for setting values 53 | explicit StringLookupKey(const char* pStr, uint32_t len, uint32_t owned, LookupHash hash): 54 | m_pStr(pStr), m_len(len), m_owned(owned), m_hash(hash) {} 55 | 56 | const char* str() const { 57 | return m_pStr; 58 | } 59 | 60 | uint32_t len() const { 61 | return m_len; 62 | } 63 | 64 | bool owned() const { 65 | return m_owned == 1; 66 | } 67 | 68 | uint32_t hash() const { 69 | return m_hash.hash; 70 | } 71 | 72 | bool operator==(const StringLookupKey& other) const { 73 | // Hash doesn't match we don't have a match. 74 | // Hash collisions are expected to be very unlikely so optimize for this case. 75 | if GAIA_LIKELY (m_hash != other.m_hash) 76 | return false; 77 | 78 | // Lengths have to match 79 | if (m_len != other.m_len) 80 | return false; 81 | 82 | // Contents have to match 83 | const auto l = m_len; 84 | GAIA_ASSUME(l < MaxLen); 85 | GAIA_FOR(l) { 86 | if (m_pStr[i] != other.m_pStr[i]) 87 | return false; 88 | } 89 | 90 | return true; 91 | } 92 | 93 | bool operator!=(const StringLookupKey& other) const { 94 | return !operator==(other); 95 | } 96 | }; 97 | } // namespace core 98 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/core/iterator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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/core/span.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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 usage of this file's usage. 7 | #include "iterator.h" 8 | #include "utility.h" 9 | 10 | #if GAIA_USE_STD_SPAN 11 | #include 12 | #else 13 | #include "impl/span_impl.h" 14 | namespace std { 15 | using gaia::core::span; 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /include/gaia/core/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include "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 -------------------------------------------------------------------------------- /include/gaia/ecs/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "../core/span.h" 7 | #include "command_buffer_fwd.h" 8 | #include "id_fwd.h" 9 | #include "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 -------------------------------------------------------------------------------- /include/gaia/ecs/api.inl: -------------------------------------------------------------------------------- 1 | #include "../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 | 136 | } // namespace ecs 137 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/archetype_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "../cnt/darray.h" 7 | #include "../cnt/map.h" 8 | #include "../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/ecs/archetype_graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "../cnt/map.h" 7 | #include "../config/logging.h" 8 | #include "api.h" 9 | #include "archetype_common.h" 10 | #include "component.h" 11 | #include "id.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 | void add_edge_right(Entity entity, ArchetypeId archetypeId, ArchetypeIdHash hash) { 58 | add_edge(m_edgesAdd, entity, archetypeId, hash); 59 | } 60 | 61 | //! Creates a "del" edge in the graph leading to the target archetype. 62 | //! \param entity Edge entity. 63 | //! \param archetypeId Target archetype. 64 | void add_edge_left(Entity entity, ArchetypeId archetypeId, ArchetypeIdHash hash) { 65 | add_edge(m_edgesDel, entity, archetypeId, hash); 66 | } 67 | 68 | //! Deletes the "add" edge formed by the entity \param entity. 69 | void del_edge_right(Entity entity) { 70 | del_edge(m_edgesAdd, entity); 71 | } 72 | 73 | //! Deletes the "del" edge formed by the entity \param entity. 74 | void del_edge_left(Entity entity) { 75 | del_edge(m_edgesDel, entity); 76 | } 77 | 78 | //! Checks if an archetype graph "add" edge with entity \param entity exists. 79 | //! \return Archetype id of the target archetype if the edge is found. ArchetypeGraphEdgeBad otherwise. 80 | GAIA_NODISCARD ArchetypeGraphEdge find_edge_right(Entity entity) const { 81 | return find_edge(m_edgesAdd, entity); 82 | } 83 | 84 | //! Checks if an archetype graph "del" edge with entity \param entity exists. 85 | //! \return Archetype id of the target archetype if the edge is found. ArchetypeGraphEdgeBad otherwise. 86 | GAIA_NODISCARD ArchetypeGraphEdge find_edge_left(Entity entity) const { 87 | return find_edge(m_edgesDel, entity); 88 | } 89 | 90 | GAIA_NODISCARD auto& right_edges() { 91 | return m_edgesAdd; 92 | } 93 | 94 | GAIA_NODISCARD const auto& right_edges() const { 95 | return m_edgesAdd; 96 | } 97 | 98 | GAIA_NODISCARD auto& left_edges() { 99 | return m_edgesDel; 100 | } 101 | 102 | GAIA_NODISCARD const auto& left_edges() const { 103 | return m_edgesDel; 104 | } 105 | 106 | void diag(const World& world) const { 107 | auto diagEdge = [&](const auto& edges) { 108 | for (const auto& edge: edges) { 109 | const auto entity = edge.first.entity(); 110 | if (entity.pair()) { 111 | const auto* name0 = entity_name(world, entity.id()); 112 | const auto* name1 = entity_name(world, entity.gen()); 113 | GAIA_LOG_N( 114 | " pair [%u:%u], %s -> %s, aid:%u", 115 | // 116 | entity.id(), entity.gen(), name0, name1, edge.second.id); 117 | } else { 118 | const auto* name = entity_name(world, entity); 119 | GAIA_LOG_N( 120 | " ent [%u:%u], %s [%s], aid:%u", 121 | // 122 | entity.id(), entity.gen(), name, EntityKindString[entity.kind()], edge.second.id); 123 | } 124 | } 125 | }; 126 | 127 | // Add edges (movement towards the leafs) 128 | if (!m_edgesAdd.empty()) { 129 | GAIA_LOG_N(" Add edges - count:%u", (uint32_t)m_edgesAdd.size()); 130 | diagEdge(m_edgesAdd); 131 | } 132 | 133 | // Delete edges (movement towards the root) 134 | if (!m_edgesDel.empty()) { 135 | GAIA_LOG_N(" Del edges - count:%u", (uint32_t)m_edgesDel.size()); 136 | diagEdge(m_edgesDel); 137 | } 138 | } 139 | }; 140 | } // namespace ecs 141 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/chunk_header.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "../cnt/bitset.h" 7 | #include "../core/utility.h" 8 | #include "archetype_common.h" 9 | #include "chunk_allocator.h" 10 | #include "component.h" 11 | #include "id.h" 12 | 13 | namespace gaia { 14 | namespace ecs { 15 | class World; 16 | class ComponentCache; 17 | struct ComponentCacheItem; 18 | 19 | struct 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 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 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 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(1) - 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 | //! Chunk size type. This tells whether it's 8K or 16K 88 | uint16_t sizeType : 1; 89 | //! When it hits 0 the chunk is scheduled for deletion 90 | uint16_t lifespanCountdown: CHUNK_LIFESPAN_BITS; 91 | //! True if deleted, false otherwise 92 | uint16_t dead : 1; 93 | //! Empty space for future use 94 | uint16_t unused : 11; 95 | 96 | //! Number of generic entities/components 97 | uint8_t genEntities; 98 | //! Number of components on the archetype 99 | uint8_t cntEntities; 100 | //! Version of the world (stable pointer to parent world's world version) 101 | uint32_t& worldVersion; 102 | 103 | static inline uint32_t s_worldVersionDummy = 0; 104 | ChunkHeader(): worldVersion(s_worldVersionDummy) {} 105 | 106 | ChunkHeader( 107 | const World& wld, const ComponentCache& compCache, uint32_t chunkIndex, uint16_t cap, uint8_t genEntitiesCnt, 108 | uint16_t st, uint32_t& version): 109 | world(&wld), cc(&compCache), index(chunkIndex), count(0), countEnabled(0), capacity(cap), 110 | // 111 | rowFirstEnabledEntity(0), hasAnyCustomGenCtor(0), hasAnyCustomUniCtor(0), hasAnyCustomGenDtor(0), 112 | hasAnyCustomUniDtor(0), sizeType(st), lifespanCountdown(0), dead(0), unused(0), 113 | // 114 | genEntities(genEntitiesCnt), cntEntities(0), worldVersion(version) { 115 | // Make sure the alignment is right 116 | GAIA_ASSERT(uintptr_t(this) % (sizeof(size_t)) == 0); 117 | } 118 | 119 | bool has_disabled_entities() const { 120 | return rowFirstEnabledEntity > 0; 121 | } 122 | 123 | bool has_enabled_entities() const { 124 | return countEnabled > 0; 125 | } 126 | }; 127 | } // namespace ecs 128 | } // namespace gaia 129 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /include/gaia/ecs/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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 | -------------------------------------------------------------------------------- /include/gaia/ecs/component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "../core/hashing_policy.h" 8 | #include "../core/utility.h" 9 | #include "../mem/data_layout_policy.h" 10 | #include "../meta/type_info.h" 11 | #include "id.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 | } // namespace ecs 156 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/component_desc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../core/span.h" 10 | #include "../core/utility.h" 11 | #include "../mem/data_layout_policy.h" 12 | #include "../mem/mem_utils.h" 13 | #include "../meta/reflection.h" 14 | #include "../meta/type_info.h" 15 | #include "component.h" 16 | 17 | namespace gaia { 18 | namespace ecs { 19 | namespace detail { 20 | using ComponentDescId = uint32_t; 21 | 22 | template 23 | struct ComponentDesc final { 24 | using CT = component_type_t; 25 | using U = typename component_type_t::Type; 26 | using DescU = typename CT::TypeFull; 27 | 28 | static ComponentDescId id() { 29 | return meta::type_info::id(); 30 | } 31 | 32 | static constexpr ComponentLookupHash hash_lookup() { 33 | return {meta::type_info::hash()}; 34 | } 35 | 36 | static constexpr auto name() { 37 | return meta::type_info::name(); 38 | } 39 | 40 | static constexpr uint32_t size() { 41 | if constexpr (std::is_empty_v) 42 | return 0; 43 | else 44 | return (uint32_t)sizeof(U); 45 | } 46 | 47 | static constexpr uint32_t alig() { 48 | constexpr auto alig = mem::auto_view_policy::Alignment; 49 | static_assert(alig < Component::MaxAlignment, "Maximum supported alignment for a component is MaxAlignment"); 50 | return alig; 51 | } 52 | 53 | static uint32_t soa(std::span soaSizes) { 54 | if constexpr (mem::is_soa_layout_v) { 55 | uint32_t i = 0; 56 | using TTuple = decltype(meta::struct_to_tuple(std::declval())); 57 | // is_soa_layout_v is always false for empty types so we know there is at least one element in the tuple 58 | constexpr auto TTupleSize = std::tuple_size_v; 59 | static_assert(TTupleSize > 0); 60 | static_assert(TTupleSize <= meta::StructToTupleMaxTypes); 61 | core::each_tuple([&](auto&& item) { 62 | static_assert(sizeof(item) <= 255, "Each member of a SoA component can be at most 255 B long!"); 63 | soaSizes[i] = (uint8_t)sizeof(item); 64 | ++i; 65 | }); 66 | GAIA_ASSERT(i <= meta::StructToTupleMaxTypes); 67 | return i; 68 | } else { 69 | return 0U; 70 | } 71 | } 72 | 73 | static constexpr auto func_ctor() { 74 | if constexpr (!mem::is_soa_layout_v && !std::is_trivially_constructible_v) { 75 | return [](void* ptr, uint32_t cnt) { 76 | core::call_ctor_n((U*)ptr, cnt); 77 | }; 78 | } else { 79 | return nullptr; 80 | } 81 | } 82 | 83 | static constexpr auto func_dtor() { 84 | if constexpr (!mem::is_soa_layout_v && !std::is_trivially_destructible_v) { 85 | return [](void* ptr, uint32_t cnt) { 86 | core::call_dtor_n((U*)ptr, cnt); 87 | }; 88 | } else { 89 | return nullptr; 90 | } 91 | } 92 | 93 | static constexpr auto func_copy_ctor() { 94 | return [](void* GAIA_RESTRICT dst, const void* GAIA_RESTRICT src, uint32_t idxDst, uint32_t idxSrc, 95 | uint32_t sizeDst, uint32_t sizeSrc) { 96 | mem::copy_ctor_element((uint8_t*)dst, (const uint8_t*)src, idxDst, idxSrc, sizeDst, sizeSrc); 97 | }; 98 | } 99 | 100 | static constexpr auto func_move_ctor() { 101 | return [](void* GAIA_RESTRICT dst, void* GAIA_RESTRICT src, uint32_t idxDst, uint32_t idxSrc, 102 | uint32_t sizeDst, uint32_t sizeSrc) { 103 | mem::move_ctor_element((uint8_t*)dst, (uint8_t*)src, idxDst, idxSrc, sizeDst, sizeSrc); 104 | }; 105 | } 106 | 107 | static constexpr auto func_copy() { 108 | return [](void* GAIA_RESTRICT dst, const void* GAIA_RESTRICT src, uint32_t idxDst, uint32_t idxSrc, 109 | uint32_t sizeDst, uint32_t sizeSrc) { 110 | mem::copy_element((uint8_t*)dst, (const uint8_t*)src, idxDst, idxSrc, sizeDst, sizeSrc); 111 | }; 112 | } 113 | 114 | static constexpr auto func_move() { 115 | return [](void* GAIA_RESTRICT dst, void* GAIA_RESTRICT src, uint32_t idxDst, uint32_t idxSrc, 116 | uint32_t sizeDst, uint32_t sizeSrc) { 117 | mem::move_element((uint8_t*)dst, (uint8_t*)src, idxDst, idxSrc, sizeDst, sizeSrc); 118 | }; 119 | } 120 | 121 | static constexpr auto func_swap() { 122 | return [](void* GAIA_RESTRICT left, void* GAIA_RESTRICT right, uint32_t idxLeft, uint32_t idxRight, 123 | uint32_t sizeLeft, uint32_t sizeRight) { 124 | mem::swap_elements((uint8_t*)left, (uint8_t*)right, idxLeft, idxRight, sizeLeft, sizeRight); 125 | }; 126 | } 127 | 128 | static constexpr auto func_cmp() { 129 | if constexpr (mem::is_soa_layout_v) { 130 | return []([[maybe_unused]] const void* left, [[maybe_unused]] const void* right) { 131 | GAIA_ASSERT(false && "func_cmp for SoA not implemented yet"); 132 | return false; 133 | }; 134 | } else { 135 | constexpr bool hasGlobalCmp = core::has_global_equals::value; 136 | constexpr bool hasMemberCmp = core::has_member_equals::value; 137 | if constexpr (hasGlobalCmp || hasMemberCmp) { 138 | return [](const void* left, const void* right) { 139 | const auto* l = (const U*)left; 140 | const auto* r = (const U*)right; 141 | return *l == *r; 142 | }; 143 | } else { 144 | // fallback comparison function 145 | return [](const void* left, const void* right) { 146 | const auto* l = (const U*)left; 147 | const auto* r = (const U*)right; 148 | return memcmp(l, r, sizeof(U)) == 0; 149 | }; 150 | } 151 | } 152 | } 153 | }; 154 | } // namespace detail 155 | } // namespace ecs 156 | } // namespace gaia 157 | -------------------------------------------------------------------------------- /include/gaia/ecs/component_getter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "chunk.h" 7 | #include "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 \tparam T on \param 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/component_setter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "chunk.h" 7 | #include "component.h" 8 | #include "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 \param 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/data_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "../cnt/darray_ext.h" 7 | #include "../mem/mem_utils.h" 8 | #include "component_cache.h" 9 | 10 | namespace gaia { 11 | namespace ecs { 12 | namespace detail { 13 | static constexpr uint32_t SerializationBufferCapacityIncreaseSize = 128U; 14 | 15 | template 16 | class SerializationBufferImpl { 17 | // Increase the capacity by multiples of CapacityIncreaseSize 18 | static constexpr uint32_t CapacityIncreaseSize = SerializationBufferCapacityIncreaseSize; 19 | 20 | //! Buffer holding raw data 21 | DataContainer m_data; 22 | //! Current position in the buffer 23 | uint32_t m_dataPos = 0; 24 | 25 | public: 26 | void reset() { 27 | m_dataPos = 0; 28 | m_data.clear(); 29 | } 30 | 31 | //! Returns the number of bytes written in the buffer 32 | GAIA_NODISCARD uint32_t bytes() const { 33 | return (uint32_t)m_data.size(); 34 | } 35 | 36 | //! Returns true if there is no data written in the buffer 37 | GAIA_NODISCARD bool empty() const { 38 | return m_data.empty(); 39 | } 40 | 41 | //! Makes sure there is enough capacity in our data container to hold another \param size bytes of data 42 | void reserve(uint32_t size) { 43 | const auto nextSize = m_dataPos + size; 44 | if (nextSize <= bytes()) 45 | return; 46 | 47 | // Make sure there is enough capacity to hold our data 48 | const auto newSize = bytes() + size; 49 | const auto newCapacity = (newSize / CapacityIncreaseSize) * CapacityIncreaseSize + CapacityIncreaseSize; 50 | m_data.reserve(newCapacity); 51 | } 52 | 53 | //! Changes the current position in the buffer 54 | void seek(uint32_t pos) { 55 | m_dataPos = pos; 56 | } 57 | 58 | //! Returns the current position in the buffer 59 | GAIA_NODISCARD uint32_t tell() const { 60 | return m_dataPos; 61 | } 62 | 63 | //! Writes \param value to the buffer 64 | template 65 | void save(T&& value) { 66 | reserve(sizeof(T)); 67 | 68 | m_data.resize(m_dataPos + sizeof(T)); 69 | mem::unaligned_ref mem((void*)&m_data[m_dataPos]); 70 | mem = GAIA_FWD(value); 71 | 72 | m_dataPos += sizeof(T); 73 | } 74 | 75 | //! Writes \param size bytes of data starting at the address \param pSrc to the buffer 76 | void save(const void* pSrc, uint32_t size) { 77 | reserve(size); 78 | 79 | // Copy "size" bytes of raw data starting at pSrc 80 | m_data.resize(m_dataPos + size); 81 | memcpy((void*)&m_data[m_dataPos], pSrc, size); 82 | 83 | m_dataPos += size; 84 | } 85 | 86 | //! Writes \param value to the buffer 87 | template 88 | void save_comp(const ComponentCacheItem& item, T&& value) { 89 | const bool isManualDestroyNeeded = item.func_copy_ctor != nullptr || item.func_move_ctor != nullptr; 90 | constexpr bool isRValue = std::is_rvalue_reference_v; 91 | 92 | reserve(sizeof(isManualDestroyNeeded) + sizeof(T)); 93 | save(isManualDestroyNeeded); 94 | m_data.resize(m_dataPos + sizeof(T)); 95 | 96 | auto* pSrc = (void*)&value; // TODO: GAIA_FWD(value)? 97 | auto* pDst = (void*)&m_data[m_dataPos]; 98 | if (isRValue && item.func_move_ctor != nullptr) { 99 | if constexpr (mem::is_movable()) 100 | mem::detail::move_ctor_element_aos((T*)pDst, (T*)pSrc, 0, 0); 101 | else 102 | mem::detail::copy_ctor_element_aos((T*)pDst, (const T*)pSrc, 0, 0); 103 | } else 104 | mem::detail::copy_ctor_element_aos((T*)pDst, (const T*)pSrc, 0, 0); 105 | 106 | m_dataPos += sizeof(T); 107 | } 108 | 109 | //! Loads \param value from the buffer 110 | template 111 | void load(T& value) { 112 | GAIA_ASSERT(m_dataPos + sizeof(T) <= bytes()); 113 | 114 | const auto& cdata = std::as_const(m_data); 115 | value = mem::unaligned_ref((void*)&cdata[m_dataPos]); 116 | 117 | m_dataPos += sizeof(T); 118 | } 119 | 120 | //! Loads \param size bytes of data from the buffer and writes them to the address \param pDst 121 | void load(void* pDst, uint32_t size) { 122 | GAIA_ASSERT(m_dataPos + size <= bytes()); 123 | 124 | const auto& cdata = std::as_const(m_data); 125 | memmove(pDst, (const void*)&cdata[m_dataPos], size); 126 | 127 | m_dataPos += size; 128 | } 129 | 130 | //! Loads \param value from the buffer 131 | void load_comp(const ComponentCache& cc, void* pDst, Entity entity) { 132 | bool isManualDestroyNeeded = false; 133 | load(isManualDestroyNeeded); 134 | 135 | const auto& desc = cc.get(entity); 136 | GAIA_ASSERT(m_dataPos + desc.comp.size() <= bytes()); 137 | const auto& cdata = std::as_const(m_data); 138 | auto* pSrc = (void*)&cdata[m_dataPos]; 139 | desc.move(pDst, pSrc, 0, 0, 1, 1); 140 | if (isManualDestroyNeeded) 141 | desc.dtor(pSrc); 142 | 143 | m_dataPos += desc.comp.size(); 144 | } 145 | }; 146 | } // namespace detail 147 | 148 | using SerializationBuffer_DArrExt = cnt::darray_ext; 149 | using SerializationBuffer_DArr = cnt::darray; 150 | 151 | class SerializationBuffer: public detail::SerializationBufferImpl {}; 152 | class SerializationBufferDyn: public detail::SerializationBufferImpl {}; 153 | } // namespace ecs 154 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/data_buffer_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace gaia { 4 | namespace ecs { 5 | class SerializationBuffer; 6 | class SerializationBufferDyn; 7 | } // namespace ecs 8 | } // namespace gaia -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /include/gaia/ecs/query_fwd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "data_buffer_fwd.h" 5 | 6 | namespace gaia { 7 | namespace ecs { 8 | class World; 9 | class Archetype; 10 | struct Entity; 11 | 12 | using QueryId = uint32_t; 13 | using GroupId = uint32_t; 14 | using QuerySerBuffer = SerializationBufferDyn; 15 | 16 | using TSortByFunc = int (*)(const World&, const void*, const void*); 17 | using TGroupByFunc = GroupId (*)(const World&, const Archetype&, Entity); 18 | } // namespace ecs 19 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/ecs/query_mask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include "component.h" 5 | #include "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]; 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 | for (uint32_t i = 0; i < 4; ++i) { 37 | const uint64_t bit = (entity.id() * s_ct_queryMask_primes[i]) >> (64 - 4); // pick 1 bit in each 16-bit block 38 | mask.value[i] = (1ull << bit); 39 | } 40 | return mask; 41 | } 42 | 43 | //! Builds a partitioned bloom mask from a list of entity IDs 44 | GAIA_NODISCARD inline QueryMask build_entity_mask(EntitySpan entities) { 45 | QueryMask result{}; 46 | for (auto entity: entities) { 47 | QueryMask hash = hash_entity_id(entity); 48 | for (uint32_t i = 0; i < 4; ++i) 49 | result.value[i] |= hash.value[i]; 50 | } 51 | return result; 52 | } 53 | 54 | //! Checks is there is a match between two masks 55 | GAIA_NODISCARD inline bool match_entity_mask(const QueryMask& m1, const QueryMask& m2) { 56 | return (m1.value[0] & m2.value[0]) != 0 && // 57 | (m1.value[1] & m2.value[1]) != 0 && // 58 | (m1.value[2] & m2.value[2]) != 0 && // 59 | (m1.value[3] & m2.value[3]) != 0; 60 | } 61 | #else 62 | using QueryMask = uint64_t; 63 | 64 | //! Hash an entity id into a mask 65 | GAIA_NODISCARD inline QueryMask hash_entity_id(Entity entity) { 66 | return (entity.id() * 11400714819323198485ull) >> (64 - 6); 67 | } 68 | 69 | //! Builds a bloom mask from a list of entity IDs 70 | GAIA_NODISCARD inline QueryMask build_entity_mask(EntitySpan entities) { 71 | QueryMask mask = 0; 72 | for (auto entity: entities) 73 | mask |= (1ull << hash_entity_id(entity)); 74 | 75 | return mask; 76 | } 77 | 78 | //! Checks is there is a match between two masks 79 | GAIA_NODISCARD inline bool match_entity_mask(const QueryMask& m1, const QueryMask& m2) { 80 | return (m1 & m2) != 0; 81 | } 82 | #endif 83 | } // namespace ecs 84 | } // namespace gaia 85 | -------------------------------------------------------------------------------- /include/gaia/external/random.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | The MIT License(MIT) 3 | 4 | Embedded Template Library. 5 | https://github.com/ETLCPP/etl 6 | https://www.etlcpp.com 7 | 8 | Copyright(c) 2017 jwellbelove 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files(the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions : 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ******************************************************************************/ 28 | 29 | #ifndef ECS_BENCHMARKS_BASE_RANDOM_H_ 30 | #define ECS_BENCHMARKS_BASE_RANDOM_H_ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace rnd { 38 | 39 | template 40 | std::enable_if_t< 41 | sizeof(To) == sizeof(From) && std::is_trivially_copyable_v && std::is_trivially_copyable_v, To> 42 | // constexpr support needs compiler magic 43 | bit_cast(const From& src) noexcept { 44 | static_assert( 45 | std::is_trivially_constructible_v, "This implementation additionally requires " 46 | "destination type to be trivially constructible"); 47 | 48 | To dst; 49 | std::memcpy(&dst, &src, sizeof(To)); 50 | return dst; 51 | } 52 | 53 | /* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) 54 | To the extent possible under law, the author has dedicated all copyright 55 | and related and neighboring rights to this software to the public domain 56 | worldwide. This software is distributed without any warranty. 57 | 58 | See . 59 | */ 60 | class random_xoshiro128 { 61 | public: 62 | //*************************************************************************** 63 | /// Default constructor. 64 | /// Attempts to come up with a unique non-zero seed. 65 | //*************************************************************************** 66 | random_xoshiro128() noexcept { 67 | // An attempt to come up with a unique non-zero seed, 68 | // based on the address of the instance. 69 | const auto n = bit_cast(this); 70 | const auto seed = static_cast(n); 71 | initialise(seed); 72 | } 73 | 74 | //*************************************************************************** 75 | /// Constructor with seed value. 76 | ///\param seed The new seed value. 77 | //*************************************************************************** 78 | constexpr explicit random_xoshiro128(uint32_t seed) noexcept { 79 | initialise(seed); 80 | } 81 | 82 | //*************************************************************************** 83 | /// Initialises the sequence with a new seed value. 84 | ///\param seed The new seed value. 85 | //*************************************************************************** 86 | constexpr void initialise(uint32_t seed) noexcept { 87 | // Add the first four primes to ensure that the seed isn't zero. 88 | state[0] = seed + 3; 89 | state[1] = seed + 5; 90 | state[2] = seed + 7; 91 | state[3] = seed + 11; 92 | } 93 | 94 | //*************************************************************************** 95 | /// Get the next random_xoshiro128 number. 96 | //*************************************************************************** 97 | constexpr uint32_t operator()() noexcept { 98 | return next(); 99 | } 100 | 101 | //*************************************************************************** 102 | /// Get the next random_xoshiro128 number in a specified inclusive range. 103 | //*************************************************************************** 104 | constexpr uint32_t range(uint32_t low, uint32_t high) noexcept { 105 | const uint32_t r = high - low + 1; 106 | return (operator()() % r) + low; 107 | } 108 | 109 | public: 110 | uint32_t state[4]{}; 111 | 112 | private: 113 | /* This is xoshiro128** 1.1, one of our 32-bit all-purpose, rock-solid 114 | generators. It has excellent speed, a 115 | * state size (128 bits) that is 116 | large enough for mild parallelism, and it passes all tests we are aware 117 | of. 118 | 119 | 120 | * Note that version 1.0 had mistakenly s[0] instead of s[1] as state 121 | word passed to the scrambler. 122 | 123 | For 124 | * generating just single-precision (i.e., 32-bit) floating-point 125 | numbers, xoshiro128+ is even faster. 126 | 127 | The 128 | * state must be seeded so that it is not everywhere zero. */ 129 | 130 | static inline constexpr uint32_t rotl(const uint32_t x, int k) noexcept { 131 | return (x << k) | (x >> (32 - k)); 132 | } 133 | 134 | constexpr uint32_t next() noexcept { 135 | const uint32_t result = rotl(state[1] * 5, 7) * 9; 136 | 137 | const uint32_t t = state[1] << 9; 138 | 139 | state[2] ^= state[0]; 140 | state[3] ^= state[1]; 141 | state[1] ^= state[2]; 142 | state[0] ^= state[3]; 143 | 144 | state[2] ^= t; 145 | 146 | state[3] = rotl(state[3], 11); 147 | 148 | return result; 149 | } 150 | 151 | /* This is the jump function for the generator. It is equivalent 152 | to 2^64 calls to next(); it can be used to 153 | * generate 2^64 154 | non-overlapping subsequences for parallel computations. */ 155 | /* 156 | constexpr void jump() noexcept { 157 | constexpr uint32_t JUMP[] = {0x8764000b, 0xf542d2d3, 0x6fa035c3, 158 | * 0x77f2db5b}; 159 | 160 | uint32_t s0 = 0; 161 | uint32_t s1 = 0; 162 | uint32_t s2 = 0; 163 | uint32_t s3 = 0; 164 | for (size_t 165 | * i = 0; i < sizeof JUMP / sizeof *JUMP; i++) { 166 | for (size_t b = 0; b < 32; b++) { 167 | if (JUMP[i] & 168 | * UINT32_C(1) << b) { 169 | s0 ^= state[0]; 170 | s1 ^= state[1]; 171 | s2 ^= state[2]; 172 | s3 ^= 173 | * state[3]; 174 | } 175 | next(); 176 | } 177 | } 178 | 179 | state[0] = s0; 180 | state[1] = s1; 181 | state[2] = s2; 182 | 183 | * state[3] = s3; 184 | } 185 | */ 186 | 187 | /* This is the long-jump function for the generator. It is equivalent to 188 | 2^96 calls to next(); it can be used to 189 | * generate 2^32 starting points, 190 | from each of which jump() will generate 2^32 non-overlapping 191 | subsequences 192 | * for parallel distributed computations. */ 193 | /* 194 | constexpr void long_jump() noexcept { 195 | constexpr uint32_t LONG_JUMP[] = {0xb523952e, 0x0b6f099f, 0xccf5a0ef, 196 | * 0x1c580662}; 197 | 198 | uint32_t s0 = 0; 199 | uint32_t s1 = 0; 200 | uint32_t s2 = 0; 201 | uint32_t s3 = 0; 202 | for (size_t 203 | * i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++) { 204 | for (size_t b = 0; b < 32; b++) { 205 | if 206 | * (LONG_JUMP[i] & UINT32_C(1) << b) { 207 | s0 ^= state[0]; 208 | s1 ^= state[1]; 209 | s2 ^= 210 | * state[2]; 211 | s3 ^= state[3]; 212 | } 213 | next(); 214 | } 215 | } 216 | 217 | state[0] = s0; 218 | state[1] = 219 | * s1; 220 | state[2] = s2; 221 | state[3] = s3; 222 | } 223 | */ 224 | }; 225 | 226 | } // namespace rnd 227 | 228 | #endif -------------------------------------------------------------------------------- /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 | #ifndef __SANITIZE_ADDRESS__ 26 | #define __SANITIZE_ADDRESS__ 27 | #endif 28 | #include 29 | 30 | // Poison a new contiguous block of memory 31 | #define GAIA_MEM_SANI_ADD_BLOCK(type, ptr, cap, size) \ 32 | if (ptr != nullptr) \ 33 | __sanitizer_annotate_contiguous_container( \ 34 | ptr, (unsigned char*)(ptr) + (cap) * sizeof(type), (unsigned char*)(ptr) + (cap) * sizeof(type), \ 35 | (unsigned char*)(ptr) + (size) * sizeof(type)) 36 | // Unpoison an existing contiguous block of buffer 37 | #define GAIA_MEM_SANI_DEL_BLOCK(type, ptr, cap, size) \ 38 | if (ptr != nullptr) \ 39 | __sanitizer_annotate_contiguous_container( \ 40 | ptr, (unsigned char*)(ptr) + (cap) * sizeof(type), (unsigned char*)(ptr) + (size) * sizeof(type), \ 41 | (unsigned char*)(ptr) + (cap) * sizeof(type)) 42 | 43 | // Unpoison memory for N new elements, use before adding the elements 44 | #define GAIA_MEM_SANI_PUSH_N(type, ptr, cap, size, diff) \ 45 | if (ptr != nullptr) \ 46 | __sanitizer_annotate_contiguous_container( \ 47 | ptr, (unsigned char*)(ptr) + (cap) * sizeof(type), (unsigned char*)(ptr) + (size) * sizeof(type), \ 48 | (unsigned char*)(ptr) + ((size) + (diff)) * sizeof(type)); 49 | // Poison memory for last N elements, use after removing the elements 50 | #define GAIA_MEM_SANI_POP_N(type, ptr, cap, size, diff) \ 51 | if (ptr != nullptr) \ 52 | __sanitizer_annotate_contiguous_container( \ 53 | ptr, (unsigned char*)(ptr) + (cap) * sizeof(type), (unsigned char*)(ptr) + ((size) + (diff)) * sizeof(type), \ 54 | (unsigned char*)(ptr) + (size) * sizeof(type)); 55 | 56 | // Unpoison memory for a new element, use before adding it 57 | #define GAIA_MEM_SANI_PUSH(type, ptr, cap, size) GAIA_MEM_SANI_PUSH_N(type, ptr, cap, size, 1) 58 | // Poison memory for the last elements, use after removing it 59 | #define GAIA_MEM_SANI_POP(type, ptr, cap, size) GAIA_MEM_SANI_POP_N(type, ptr, cap, size, 1) 60 | 61 | #else 62 | #define GAIA_MEM_SANI_ADD_BLOCK(type, ptr, cap, size) ((void)(ptr), (void)(cap), (void)(size)) 63 | #define GAIA_MEM_SANI_DEL_BLOCK(type, ptr, cap, size) ((void)(ptr), (void)(cap), (void)(size)) 64 | #define GAIA_MEM_SANI_PUSH_N(type, ptr, cap, size, diff) ((void)(ptr), (void)(cap), (void)(size), (void)(diff)) 65 | #define GAIA_MEM_SANI_POP_N(type, ptr, cap, size, diff) ((void)(ptr), (void)(cap), (void)(size), (void)(diff)) 66 | #define GAIA_MEM_SANI_PUSH(type, ptr, cap, size) ((void)(ptr), (void)(cap), (void)(size)) 67 | #define GAIA_MEM_SANI_POP(type, ptr, cap, size) ((void)(ptr), (void)(cap), (void)(size)) 68 | #endif 69 | -------------------------------------------------------------------------------- /include/gaia/mem/raw_data_holder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "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 -------------------------------------------------------------------------------- /include/gaia/mem/stack_allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | #include "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/meta/type_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include "../core/hashing_policy.h" 5 | #include "../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/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 | -------------------------------------------------------------------------------- /include/gaia/mt/event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | #include "../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 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 | m_set = true; 54 | m_cv.notify_one(); 55 | #else 56 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 57 | GAIA_ASSERT(ret == 0); 58 | m_set = true; 59 | 60 | // Depending on the event type, we either trigger everyone waiting or just one 61 | ret = pthread_cond_signal(&m_hCondHandle); 62 | GAIA_ASSERT(ret == 0); 63 | 64 | ret = pthread_mutex_unlock(&m_hMutexHandle); 65 | GAIA_ASSERT(ret == 0); 66 | #endif 67 | } 68 | 69 | void reset() { 70 | #if GAIA_USE_MT_STD 71 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 72 | std::unique_lock lock(mtx); 73 | m_set = false; 74 | #else 75 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 76 | GAIA_ASSERT(ret == 0); 77 | m_set = false; 78 | ret = pthread_mutex_unlock(&m_hMutexHandle); 79 | GAIA_ASSERT(ret == 0); 80 | #endif 81 | } 82 | 83 | GAIA_NODISCARD bool is_set() { 84 | #if GAIA_USE_MT_STD 85 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 86 | std::unique_lock lock(mtx); 87 | return m_set; 88 | #else 89 | bool set{}; 90 | [[maybe_unused]] int ret = pthread_mutex_lock(&m_hMutexHandle); 91 | GAIA_ASSERT(ret == 0); 92 | set = m_set; 93 | ret = pthread_mutex_unlock(&m_hMutexHandle); 94 | GAIA_ASSERT(ret == 0); 95 | return set; 96 | #endif 97 | } 98 | 99 | void wait() { 100 | #if GAIA_USE_MT_STD 101 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_mtx); 102 | std::unique_lock lock(mtx); 103 | m_cv.wait(lock, [&] { 104 | return m_set; 105 | }); 106 | #else 107 | [[maybe_unused]] int ret{}; 108 | auto wait = [&]() { 109 | if (!m_set) { 110 | do { 111 | ret = pthread_cond_wait(&m_hCondHandle, &m_hMutexHandle); 112 | } while (!ret && !m_set); 113 | 114 | GAIA_ASSERT(ret != EINVAL); 115 | if (!ret) 116 | m_set = false; 117 | } else { 118 | ret = 0; 119 | } 120 | 121 | return ret; 122 | }; 123 | 124 | ret = pthread_mutex_lock(&m_hMutexHandle); 125 | GAIA_ASSERT(ret == 0); 126 | 127 | int res = wait(); // true: signaled, false: timeout or error 128 | (void)res; 129 | 130 | ret = pthread_mutex_unlock(&m_hMutexHandle); 131 | GAIA_ASSERT(ret == 0); 132 | #endif 133 | } 134 | }; // namespace mt 135 | } // namespace mt 136 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/mt/futex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | #include "../config/profiler.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "event.h" 9 | 10 | namespace gaia { 11 | namespace mt { 12 | namespace detail { 13 | inline static constexpr uint32_t WaitMaskAll = 0x7FFFFFFF; 14 | inline static constexpr uint32_t WaitMaskAny = ~0u; 15 | 16 | struct FutexWaitNode { 17 | FutexWaitNode* pNext = nullptr; 18 | const std::atomic_uint32_t* pFutexValue = nullptr; 19 | uint32_t waitMask = WaitMaskAny; 20 | Event evt; 21 | }; 22 | 23 | struct FutexBucket { 24 | GAIA_PROF_MUTEX(std::mutex, mtx); 25 | FutexWaitNode* pFirst = nullptr; 26 | 27 | // Since there shouldn't be that many threads waiting at any one time, this seems like a good 28 | // number of hash table buckets. Making it prime number for better spread. 29 | static constexpr uint32_t BUCKET_SIZE = 37; 30 | 31 | static FutexBucket& get(const std::atomic_uint32_t* pFutexValue) { 32 | static FutexBucket s_buckets[BUCKET_SIZE]; 33 | return s_buckets[(uintptr_t(pFutexValue) >> 2) % BUCKET_SIZE]; 34 | } 35 | }; 36 | 37 | inline thread_local FutexWaitNode t_WaitNode; 38 | 39 | } // namespace detail 40 | 41 | //! An implementation of a simple futex (fast userspace mutex). 42 | //! Only wait and wake are implemented. 43 | //! 44 | //! The main advantage of futex is performance. It avoids kernel involvement in uncontended cases. 45 | //! When there’s no contention, futexes allow threads to lock and unlock in userspace without entering 46 | //! the kernel, making operations significantly faster and reducing context-switch overhead. 47 | //! Only when there is contention does a futex use the kernel to put threads to sleep and wake them up, 48 | //! resulting in a hybrid model that is more efficient than mutexes, which always require kernel calls. 49 | //! 50 | //! TODO: Consider using WaitOnAddress for Windows, futex call for Linux etc. 51 | //! The current solution is platform-agnostic but platform-specific solutions might be more performant. 52 | struct Futex { 53 | enum class Result { 54 | //! Futex value didn't match the expected one 55 | Change, 56 | //! Futex woken up as a result of wake() 57 | WakeUp 58 | }; 59 | 60 | //! \param pFutexValue Target futex 61 | //! \param expected Expected futex value 62 | //! \param wakeMask Mask of waiters to wait for 63 | static Result wait(const std::atomic_uint32_t* pFutexValue, uint32_t expected, uint32_t waitMask) { 64 | GAIA_PROF_SCOPE(futex::wait); 65 | 66 | GAIA_ASSERT(waitMask != 0); 67 | 68 | auto& bucket = detail::FutexBucket::get(pFutexValue); 69 | auto& node = detail::t_WaitNode; 70 | node.pFutexValue = pFutexValue; 71 | node.waitMask = waitMask; 72 | 73 | { 74 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx); 75 | std::lock_guard lock(mtx); 76 | 77 | const uint32_t futexValue = pFutexValue->load(std::memory_order_relaxed); 78 | if (futexValue != expected) 79 | return Result::Change; 80 | 81 | node.pNext = bucket.pFirst; 82 | bucket.pFirst = &node; 83 | } 84 | 85 | node.evt.wait(); 86 | return Result::WakeUp; 87 | } 88 | 89 | //! \param pFutexValue Target futex 90 | //! \param wakeCount How many waiters are supposed to make up 91 | //! \param wakeMask Mask of callers to wake 92 | static uint32_t 93 | wake(const std::atomic_uint32_t* pFutexValue, uint32_t wakeCount, uint32_t wakeMask = detail::WaitMaskAny) { 94 | GAIA_PROF_SCOPE(futex::wake); 95 | 96 | GAIA_ASSERT(wakeMask != 0); 97 | 98 | auto& bucket = detail::FutexBucket::get(pFutexValue); 99 | auto& mtx = GAIA_PROF_EXTRACT_MUTEX(bucket.mtx); 100 | std::lock_guard lock(mtx); 101 | 102 | uint32_t numAwoken = 0; 103 | auto** ppNode = &bucket.pFirst; 104 | for (auto* pNode = *ppNode; numAwoken < wakeCount && pNode != nullptr; pNode = *ppNode) { 105 | if (pNode->pFutexValue == pFutexValue && (pNode->waitMask & wakeMask) != 0) { 106 | ++numAwoken; 107 | 108 | // Unlink the node 109 | *ppNode = pNode->pNext; 110 | pNode->pNext = nullptr; 111 | 112 | pNode->evt.set(); 113 | } else { 114 | ppNode = &pNode->pNext; 115 | } 116 | } 117 | 118 | return numAwoken; 119 | } 120 | }; 121 | } // namespace mt 122 | } // 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 "../core/utility.h" 8 | 9 | #include "event.h" 10 | #include "jobqueue.h" 11 | 12 | namespace gaia { 13 | namespace mt { 14 | enum class JobPriority : uint8_t { 15 | //! High priority job. If available it should target the CPU's performance cores. 16 | High = 0, 17 | //! Low priority job. If available it should target the CPU's efficiency cores. 18 | Low = 1 19 | }; 20 | static inline constexpr uint32_t JobPriorityCnt = 2; 21 | 22 | enum JobCreationFlags : uint8_t { 23 | Default = 0, 24 | //! The job is not deleted automatically. Has to be done by the used. 25 | ManualDelete = 0x01, 26 | //! The job can wait for other job (one not set as dependency). 27 | CanWait = 0x02 28 | }; 29 | 30 | struct JobAllocCtx { 31 | JobPriority priority; 32 | }; 33 | 34 | struct Job { 35 | std::function func; 36 | JobPriority priority = JobPriority::High; 37 | JobCreationFlags flags = JobCreationFlags::Default; 38 | }; 39 | 40 | struct JobArgs { 41 | uint32_t idxStart; 42 | uint32_t idxEnd; 43 | }; 44 | 45 | struct JobParallel { 46 | std::function func; 47 | JobPriority priority = JobPriority::High; 48 | }; 49 | 50 | class ThreadPool; 51 | 52 | struct ThreadCtx { 53 | //! Thread pool pointer 54 | ThreadPool* tp; 55 | //! Worker index 56 | uint32_t workerIdx; 57 | //! Job priority 58 | JobPriority prio; 59 | //! Event signaled when a job is executed 60 | Event event; 61 | //! Lock-free work stealing queue for the jobs 62 | JobQueue<512> jobQueue; 63 | 64 | ThreadCtx() = default; 65 | ~ThreadCtx() = default; 66 | 67 | void reset() { 68 | event.reset(); 69 | jobQueue.clear(); 70 | } 71 | 72 | ThreadCtx(const ThreadCtx& other) = delete; 73 | ThreadCtx& operator=(const ThreadCtx& other) = delete; 74 | ThreadCtx(ThreadCtx&& other) = delete; 75 | ThreadCtx& operator=(ThreadCtx&& other) = delete; 76 | }; 77 | } // namespace mt 78 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/mt/jobhandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../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 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/mt/semaphore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../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 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/mt/semaphore_fast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../config/config.h" 4 | 5 | #include "semaphore.h" 6 | #include 7 | 8 | namespace gaia { 9 | namespace mt { 10 | //! An optimized version of Semaphore that avoids expensive system calls when the counter is greater than 0. 11 | class SemaphoreFast final { 12 | Semaphore m_sem; 13 | std::atomic_int32_t m_cnt; 14 | 15 | SemaphoreFast(SemaphoreFast&&) = delete; 16 | SemaphoreFast(const SemaphoreFast&) = delete; 17 | SemaphoreFast& operator=(SemaphoreFast&&) = delete; 18 | SemaphoreFast& operator=(const SemaphoreFast&) = delete; 19 | 20 | public: 21 | explicit SemaphoreFast(int32_t count = 0): m_sem(count), m_cnt(0) {} 22 | ~SemaphoreFast() = default; 23 | 24 | //! Increments semaphore count by the specified amount. 25 | void release(int32_t count = 1) { 26 | const int32_t prevCount = m_cnt.fetch_add(count, std::memory_order_release); 27 | int32_t toRelease = -prevCount; 28 | if (count < toRelease) 29 | toRelease = count; 30 | 31 | if (toRelease > 0) 32 | m_sem.release(toRelease); 33 | } 34 | 35 | //! Decrements semaphore count by 1. 36 | //! If the count is already 0, it waits indefinitely until semaphore count is incremented, 37 | //! then decrements and returns. Returns false when an error occurs, otherwise returns true. 38 | bool wait() { 39 | const int32_t oldCount = m_cnt.fetch_sub(1, std::memory_order_acquire); 40 | bool result = true; 41 | if (oldCount <= 0) 42 | result = m_sem.wait(); 43 | 44 | return result; 45 | } 46 | }; 47 | } // namespace mt 48 | } // namespace gaia -------------------------------------------------------------------------------- /include/gaia/mt/spinlock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | 6 | namespace gaia { 7 | namespace mt { 8 | class 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 -------------------------------------------------------------------------------- /include/gaia/ser/serialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../config/config.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "../core/utility.h" 8 | #include "../meta/reflection.h" 9 | 10 | namespace gaia { 11 | namespace ser { 12 | namespace detail { 13 | enum class serialization_type_id : uint8_t { 14 | // Integer types 15 | s8 = 1, 16 | u8 = 2, 17 | s16 = 3, 18 | u16 = 4, 19 | s32 = 5, 20 | u32 = 6, 21 | s64 = 7, 22 | u64 = 8, 23 | 24 | // Boolean 25 | b = 40, 26 | 27 | // Character types 28 | c8 = 41, 29 | c16 = 42, 30 | c32 = 43, 31 | cw = 44, 32 | 33 | // Floating point types 34 | f8 = 81, 35 | f16 = 82, 36 | f32 = 83, 37 | f64 = 84, 38 | f128 = 85, 39 | 40 | // Special 41 | trivial_wrapper = 200, 42 | data_and_size = 201, 43 | 44 | Last = 255, 45 | }; 46 | 47 | GAIA_DEFINE_HAS_FUNCTION(resize); 48 | GAIA_DEFINE_HAS_FUNCTION(bytes); 49 | GAIA_DEFINE_HAS_FUNCTION(save); 50 | GAIA_DEFINE_HAS_FUNCTION(load); 51 | 52 | template 53 | struct is_trivially_serializable { 54 | private: 55 | static constexpr bool update() { 56 | return std::is_enum_v || std::is_fundamental_v || std::is_trivially_copyable_v; 57 | } 58 | 59 | public: 60 | static inline constexpr bool value = update(); 61 | }; 62 | 63 | template 64 | struct is_int_kind_id: 65 | std::disjunction< 66 | std::is_same, std::is_same, // 67 | std::is_same, std::is_same, // 68 | std::is_same, std::is_same, // 69 | std::is_same, std::is_same, // 70 | std::is_same> {}; 71 | 72 | template 73 | struct is_flt_kind_id: 74 | std::disjunction< 75 | // std::is_same, // 76 | // std::is_same, // 77 | std::is_same, // 78 | std::is_same, // 79 | std::is_same> {}; 80 | 81 | template 82 | GAIA_NODISCARD constexpr serialization_type_id int_kind_id() { 83 | static_assert(is_int_kind_id::value, "Unsupported integral type"); 84 | 85 | if constexpr (std::is_same_v) { 86 | return serialization_type_id::s8; 87 | } else if constexpr (std::is_same_v) { 88 | return serialization_type_id::u8; 89 | } else if constexpr (std::is_same_v) { 90 | return serialization_type_id::s16; 91 | } else if constexpr (std::is_same_v) { 92 | return serialization_type_id::u16; 93 | } else if constexpr (std::is_same_v) { 94 | return serialization_type_id::s32; 95 | } else if constexpr (std::is_same_v) { 96 | return serialization_type_id::u32; 97 | } else if constexpr (std::is_same_v) { 98 | return serialization_type_id::s64; 99 | } else if constexpr (std::is_same_v) { 100 | return serialization_type_id::u64; 101 | } else { // if constexpr (std::is_same_v) { 102 | return serialization_type_id::b; 103 | } 104 | } 105 | 106 | template 107 | GAIA_NODISCARD constexpr serialization_type_id flt_type_id() { 108 | static_assert(is_flt_kind_id::value, "Unsupported floating type"); 109 | 110 | // if constexpr (std::is_same_v) { 111 | // return serialization_type_id::f8; 112 | // } else if constexpr (std::is_same_v) { 113 | // return serialization_type_id::f16; 114 | // } else 115 | if constexpr (std::is_same_v) { 116 | return serialization_type_id::f32; 117 | } else if constexpr (std::is_same_v) { 118 | return serialization_type_id::f64; 119 | } else { // if constexpr (std::is_same_v) { 120 | return serialization_type_id::f128; 121 | } 122 | } 123 | 124 | template 125 | GAIA_NODISCARD constexpr serialization_type_id type_id() { 126 | if constexpr (std::is_enum_v) 127 | return int_kind_id>(); 128 | else if constexpr (std::is_integral_v) 129 | return int_kind_id(); 130 | else if constexpr (std::is_floating_point_v) 131 | return flt_type_id(); 132 | else if constexpr (core::has_data_and_size::value) 133 | return serialization_type_id::data_and_size; 134 | else if constexpr (std::is_class_v) 135 | return serialization_type_id::trivial_wrapper; 136 | 137 | return serialization_type_id::Last; 138 | } 139 | 140 | template 141 | GAIA_NODISCARD constexpr uint32_t bytes_one(const T& item) noexcept { 142 | using U = core::raw_t; 143 | 144 | constexpr auto id = type_id(); 145 | static_assert(id != serialization_type_id::Last); 146 | uint32_t size_in_bytes{}; 147 | 148 | // Custom bytes() has precedence 149 | if constexpr (has_bytes::value) { 150 | size_in_bytes = (uint32_t)item.bytes(); 151 | } 152 | // Trivially serializable types 153 | else if constexpr (is_trivially_serializable::value) { 154 | size_in_bytes = (uint32_t)sizeof(U); 155 | } 156 | // Types which have data() and size() member functions 157 | else if constexpr (core::has_data_and_size::value) { 158 | size_in_bytes = (uint32_t)item.size(); 159 | } 160 | // Classes 161 | else if constexpr (std::is_class_v) { 162 | meta::each_member(item, [&](auto&&... items) { 163 | size_in_bytes += (bytes_one(items) + ...); 164 | }); 165 | } else 166 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 167 | 168 | return size_in_bytes; 169 | } 170 | 171 | template 172 | void ser_data_one(Serializer& s, T&& arg) { 173 | using U = core::raw_t; 174 | 175 | // Custom save() & load() have precedence 176 | if constexpr (Write && has_save::value) { 177 | arg.save(s); 178 | } else if constexpr (!Write && has_load::value) { 179 | arg.load(s); 180 | } 181 | // Trivially serializable types 182 | else if constexpr (is_trivially_serializable::value) { 183 | if constexpr (Write) 184 | s.save(GAIA_FWD(arg)); 185 | else 186 | s.load(GAIA_FWD(arg)); 187 | } 188 | // Types which have data() and size() member functions 189 | else if constexpr (core::has_data_and_size::value) { 190 | if constexpr (Write) { 191 | const auto size = arg.size(); 192 | s.save(size); 193 | 194 | for (const auto& e: arg) 195 | ser_data_one(s, e); 196 | } else { 197 | auto size = arg.size(); 198 | s.load(size); 199 | 200 | if constexpr (has_resize::value) { 201 | // If resize is present, use it 202 | arg.resize(size); 203 | for (auto& e: arg) 204 | ser_data_one(s, e); 205 | } else { 206 | // With no resize present, write directly into memory 207 | GAIA_FOR(size) { 208 | using arg_type = typename std::remove_pointer::type; 209 | auto& e_ref = (arg_type&)arg[i]; 210 | ser_data_one(s, e_ref); 211 | } 212 | } 213 | } 214 | } 215 | // Classes 216 | else if constexpr (std::is_class_v) { 217 | meta::each_member(GAIA_FWD(arg), [&s](auto&&... items) { 218 | // TODO: Handle contiguous blocks of trivially copyable types 219 | (ser_data_one(s, items), ...); 220 | }); 221 | } else 222 | static_assert(!sizeof(U), "Type is not supported for serialization, yet"); 223 | } 224 | } // namespace detail 225 | 226 | //! Calculates the number of bytes necessary to serialize data using the "save" function. 227 | //! \warning Compile-time. 228 | template 229 | GAIA_NODISCARD uint32_t bytes(const T& data) { 230 | return detail::bytes_one(data); 231 | } 232 | 233 | //! Write \param data using \tparam Writer at compile-time. 234 | //! 235 | //! \warning Writer has to implement a save function as follows: 236 | //! template void save(const T& arg); 237 | template 238 | void save(Writer& writer, const T& data) { 239 | detail::ser_data_one(writer, data); 240 | } 241 | 242 | //! Read \param data using \tparam Reader at compile-time. 243 | //! 244 | //! \warning Reader has to implement a save function as follows: 245 | //! template void load(T& arg); 246 | template 247 | void load(Reader& reader, T& data) { 248 | detail::ser_data_one(reader, data); 249 | } 250 | } // namespace ser 251 | } // namespace gaia -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/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/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/example2/src/dummy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Example source file also including gaia.h -------------------------------------------------------------------------------- /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 = 1'500; 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 = 1'000; 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 | -------------------------------------------------------------------------------- /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 ${PROJECT_SOURCE_DIR}/single_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/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/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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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_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/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 | -------------------------------------------------------------------------------- /src/perf/app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_app") 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/entity/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_entity") 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/mt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJ_NAME "gaia_perf_mt") 2 | add_executable(${PROJ_NAME} src/main.cpp) 3 | -------------------------------------------------------------------------------- /src/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | set(PROJ_NAME "gaia_test") 4 | add_executable(${PROJ_NAME} src/main.cpp) 5 | 6 | # System-specific threading library 7 | set(THREADS_PREFER_PTHREAD_FLAG ON) 8 | find_package(Threads REQUIRED) 9 | target_link_libraries(${PROJ_NAME} PRIVATE Threads::Threads) 10 | 11 | # Unit test framework 12 | target_link_libraries(${PROJ_NAME} PRIVATE Catch2::Catch2WithMain) 13 | 14 | # Project files 15 | target_include_directories(${PROJ_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include) 16 | 17 | if(MSVC) 18 | enable_cxx_compiler_flag_if_supported("/bigobj") 19 | endif() 20 | 21 | include(CTest) 22 | include(Catch) 23 | catch_discover_tests(${PROJ_NAME}) 24 | --------------------------------------------------------------------------------