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