├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Readme.md ├── conanfile.txt ├── include └── ecs-cpp │ ├── EcsCpp.h │ ├── EcsEffects.h │ ├── EcsUtil.h │ └── EntityID.h └── tests ├── CMakeLists.txt └── ECSTests.cpp /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build-macOS: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: macos-latest 19 | 20 | steps: 21 | - name: Install gtest manually 22 | run: brew install googletest 23 | - uses: actions/checkout@v3 24 | 25 | - name: Configure CMake 26 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 27 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 28 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 29 | 30 | - name: Build 31 | # Build your program with the given configuration 32 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 33 | 34 | - name: Test 35 | working-directory: ${{github.workspace}}/build/tests/ 36 | # Execute tests defined by the CMake configuration. 37 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 38 | run: ctest -C ${{env.BUILD_TYPE}} 39 | 40 | build-ubuntu: 41 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 42 | # You can convert this to a matrix build if you need cross-platform coverage. 43 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - run: | 48 | sudo apt update 49 | sudo apt install gcc-10 g++-10 50 | - name: Install gtest manually 51 | run: sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake CMakeLists.txt && sudo make && sudo cp lib/*.a /usr/lib && sudo ln -s /usr/lib/libgtest.a /usr/local/lib/libgtest.a && sudo ln -s /usr/lib/libgtest_main.a /usr/local/lib/libgtest_main.a 52 | - uses: actions/checkout@v3 53 | 54 | - name: Configure CMake 55 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 56 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 57 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 58 | env: 59 | CC: gcc-10 60 | CXX: g++-10 61 | 62 | - name: Build 63 | # Build your program with the given configuration 64 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 65 | 66 | - name: Test 67 | working-directory: ${{github.workspace}}/build/tests/ 68 | # Execute tests defined by the CMake configuration. 69 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 70 | run: ctest -C ${{env.BUILD_TYPE}} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Build folder 35 | cmake-build-* 36 | 37 | # IDE 38 | .idea 39 | .DS_Store 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20.1) 2 | project(ecs-cpp) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20") 7 | #add_compile_options(-fsanitize=address) 8 | #add_link_options(-fsanitize=address) 9 | 10 | add_library(${PROJECT_NAME} INTERFACE) 11 | target_include_directories(${PROJECT_NAME} INTERFACE "include") 12 | 13 | add_subdirectory(tests) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Stefan Annell 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 2 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/annell/ecs-cpp/cmake.yml) 3 | 4 | # ecs-cpp 5 | A fully dynamic header only Entity Component System for C++20 where components can be added or removed to entities whenever. 6 | 7 | ## What is ECS? 8 | 9 | A Entity Component System (ECS) is a design pattern that is used to decouple data from logic. It is a way to organize your code in a way that is more flexible and maintainable. It is a common pattern used in game engines, but can be used in other applications as well. 10 | 11 | See it as a container that holds entities, and each entity can have one or more components. A component is a data structure that holds data, and a system is a function that operates on entities that has a specific set of components. A system can be seen as a function that takes in a set of components and performs the operation on said components. The ECS container is responsible for keeping track of which entities has which components, and which systems should be run on which entities. 12 | 13 | From [Wikipedia](https://en.wikipedia.org/wiki/Entity_component_system): 14 | 15 | >`Entity` An entity represents a general-purpose object. In a game engine context, for example, every coarse game object is represented as an entity. Usually, it only consists of a unique id. Implementations typically use a plain integer for this. 16 | 17 | >`Component` A component labels an entity as possessing a particular aspect, and holds the data needed to model that aspect. For example, every game object that can take damage might have a Health component associated with its entity. Implementations typically use structs, classes, or associative arrays. 18 | 19 | >`System` A system is a process which acts on all entities with the desired components. For example a physics system may query for entities having mass, velocity and position components, and iterate over the results doing physics calculations on the sets of components for each entity. 20 | 21 | >The behavior of an entity can be changed at runtime by systems that add, remove or modify components. This eliminates the ambiguity problems of deep and wide inheritance hierarchies often found in Object-Oriented Programming techniques that are difficult to understand, maintain, and extend. Common ECS approaches are highly compatible with, and are often combined with, data-oriented design techniques. Data for all instances of a component are commonly stored together in physical memory, enabling efficient memory access for systems which operate over many entities. 22 | 23 | ## Features 24 | - Header only library for easy integration. 25 | - Arena memory allocator of the components allocated on the stack. 26 | - Quick iteration over entities due to it being efficiently packed which reduces cache misses. 27 | - Dynamic adding / removing of components. 28 | - Heavy use of templates and concepts to make misuse of library harder and error message clearer. 29 | - Safe concurrent processing of systems. 30 | - Extensive unit testing of library. 31 | - Separate Attributes/Effects system for easy handling of attributes and effects. 32 | 33 | ## Using this ECS 34 | Create a container, components it should handle are coded in upfront: 35 | ```c++ 36 | ecs::ECSManager ecs; 37 | ``` 38 | 39 | AddEntity an entity to the container: 40 | ```c++ 41 | ecs::EntityID entity = ecs.AddEntity(); 42 | ``` 43 | 44 | AddEntity two components to the entity: 45 | ```c++ 46 | ecs.AddEntity(entity, std::string("Hej")); 47 | ecs.AddEntity(entity, 5); 48 | ``` 49 | 50 | Or add both entities at the same time: 51 | ```c++ 52 | ecs.AddSeveral(entity, std::string("Hej"), 5); 53 | ``` 54 | 55 | Or do the two steps, adding a entity and its components, in one call: 56 | ```c++ 57 | ecs::EntityID entity = ecs.BuildEntity(std::string("Hej"), 5); 58 | ``` 59 | 60 | Fill upp and loop over a system with its active entities that has active components: 61 | ```c++ 62 | #include 63 | 64 | ecs::ECSManager ecs; 65 | auto entity1 = ecs.BuildEntity(std::string("Hello"), 1); 66 | auto entity2 = ecs.BuildEntity(2, std::string("Hello"), 5.0f); 67 | auto entity3 = ecs.BuildEntity(3, 5.0f); 68 | 69 | // Will match entity1 and entity2 70 | std::string output; 71 | for (auto [strVal, intVal]: ecs.GetSystem()) { 72 | output += strVal + " - " + std::to_string(intVal) + " "; 73 | } 74 | ASSERT_EQ(output, "Hello - 1 World - 2 "); 75 | 76 | float fsum = 0; 77 | int isum = 0; 78 | // Will match entity2 and entity3 79 | for (auto [intVal, floatVal]: ecs.GetSystem()) { 80 | isum += intVal; 81 | fsum += floatVal; 82 | } 83 | ASSERT_EQ(fsum, 10.0f); 84 | ASSERT_EQ(isum, 5); 85 | ``` 86 | 87 | ## Using Attributes / Effects system 88 | The ECS library also includes a separate system for handling attributes and effects. This system is used to handle attributes and effects in a game, where an attribute is a value that can be modified by effects. An effect is a modifier that can be applied to an attribute. The system is designed to be easy to use and flexible where you define all the attributes and effects upfront. The user of the system builds up and extends it with the effects and attributes they need. 89 | 90 | A simple example where we have a basic attribute with one effect that doubles the health of the entity. 91 | ```c++ 92 | #include 93 | struct HeroAttributes { 94 | float health = 100.0f; 95 | float speed = 1.0f; 96 | }; 97 | 98 | struct HealthBoost { 99 | float healthBoost = 2.0f; 100 | using Attribute = HeroAttributes; 101 | 102 | void Apply(Attribute& attribute) const { 103 | attribute.health *= healthBoost; 104 | } 105 | }; 106 | 107 | ecs::ECSManager ecs; 108 | ecs::AttributesEffectsManager, HealthBoost> aem; 109 | auto entity = ecs.BuildEntity(1); 110 | { 111 | auto& attributes = aem.Get(entity); 112 | ASSERT_EQ(attributes.health, 100.0f); 113 | } 114 | { 115 | auto& attributes = aem.Get(entity); 116 | aem.Add(entity, HealthBoost()); 117 | ASSERT_EQ(attributes.health, 200.0f); 118 | } 119 | { 120 | auto& attributes = aem.Get(entity); 121 | aem.Add(entity, HealthBoost()); 122 | ASSERT_EQ(attributes.health, 400.0f); 123 | } 124 | { 125 | aem.Modify(entity, HeroAttributes{10.0f, 1.0f}); 126 | auto& attributes = aem.Get(entity); 127 | ASSERT_EQ(attributes.health, 40.0f); 128 | } 129 | 130 | ``` 131 | 132 | # To install 133 | ## CMake method 134 | 1. Clone ecs-cpp to your project. 135 | 2. Add `add_subdirectory(path/ecs-cpp)` to your CMakeLists.txt. 136 | 3. Link your project to `ecs-cpp`. 137 | 4. Include `#include ` in your project. 138 | 139 | ### Dependencies 140 | - C++20 141 | 142 | ### Example on integration 143 | Here you can find a example on how to integrate this library into your project using CMake: [ecs-cpp-example](https://github.com/annell/physim-cpp). 144 | In this example, the library is placed under thirdparty/ecs-cpp. 145 | 146 | ## To run test suite 147 | 1. Clone ecs-cpp to your project. 148 | 2. Install dependencies. 149 | 3. Add `add_subdirectory(path/ecs-cpp)` to your CMakeLists.txt. 150 | 151 | ### Dependencies 152 | - C++20 153 | - GTest 154 | 155 | ### To install all dependencies using Conan [optional] 156 | This library uses the PackageManager [Conan](https://conan.io) for its dependencies, and all dependencies can be found in `conantfile.txt`. 157 | 1. Install conan `pip3 install conan` 158 | 2. Go to the build folder that cmake generates. 159 | 3. Run `conan install ..` see [installing dependencies](https://docs.conan.io/en/1.7/using_packages/conanfile_txt.html) 160 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | gtest/cci.20210126 3 | 4 | [generators] 5 | CMakeDeps 6 | CMakeToolchain -------------------------------------------------------------------------------- /include/ecs-cpp/EcsCpp.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2021-05-12. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "EntityID.h" 14 | #include "EcsUtil.h" 15 | 16 | namespace ecs { 17 | /** 18 | * ECSManager 19 | * A ECS container that keeps track of all components 20 | * and entities in a scene 21 | * 22 | * Initialized with a list of components to track: 23 | * ECSManager() 24 | * 25 | * AddEntity, Remove, Has got both entity and component 26 | * interfaces to be able to interact with both easily 27 | * depending on the need. 28 | * 29 | * GetSystem() is the main way for 30 | * systems to integrate against the ECS container as 31 | * it filters out the entities that contains the 32 | * requested components and allows for easy iteration. 33 | * @tparam TComponents list of components that ECS tracks. 34 | * TComponents needs to fufill the IsBasicType and NonVoidArgs 35 | * concepts. 36 | */ 37 | template 38 | requires NonVoidArgs && IsBasicType 39 | class ECSManager { 40 | private: 41 | using TComponentPack = std::tuple; 42 | using TECSManager = ECSManager; 43 | 44 | template 45 | using ComponentArray = std::vector; 46 | using ComponentMatrix = std::tuple...>; 47 | 48 | template 49 | struct AvailableComponent { 50 | bool active = false; 51 | }; 52 | using AvailableComponents = std::tuple...>; 53 | 54 | template 55 | struct ComponentRange { 56 | using TComponentRange = TComponent; 57 | bool componentPresent = false; 58 | size_t firstSlot = SIZE_MAX; 59 | size_t lastSlot = 0; 60 | }; 61 | using ComponentRanges = std::tuple...>; 62 | 63 | struct ComponentRangesMatch { 64 | size_t firstSlot = SIZE_MAX; 65 | size_t lastSlot = 0; 66 | }; 67 | 68 | /** 69 | * Entity 70 | * The entity in the ECS, tracks its own ID if it is 71 | * active and if it has any components added to it. 72 | */ 73 | struct Entity { 74 | AvailableComponents activeComponents{}; 75 | bool active = false; 76 | EntityID id = EntityID(0); 77 | }; 78 | using EntitiesSlots = std::vector; 79 | 80 | /** 81 | * SystemIterator 82 | * A iterator that loops over the matching components, 83 | * skipping the ones that does not have the correct 84 | * components active. 85 | * @tparam TSystemComponents list of components that 86 | * iterator tracks. 87 | */ 88 | template 89 | struct SystemIterator { 90 | private: 91 | using TInternalIterator = typename TECSManager::EntitiesSlots::const_iterator; 92 | public: 93 | [[maybe_unused]] SystemIterator(TECSManager &ecs, TInternalIterator begin, TInternalIterator end) : ecs(ecs), begin(begin), end(end) {} 94 | 95 | auto operator*() const { return ecs.template GetSeveral(begin->id); } 96 | 97 | SystemIterator &operator++() { 98 | begin++; 99 | while (begin != end) { 100 | if (ecs.HasGivenComponents(begin)) { 101 | break; 102 | } 103 | begin++; 104 | } 105 | return *this; 106 | } 107 | 108 | friend bool operator==(const SystemIterator &a, const SystemIterator &b) { return a.begin == b.begin; }; 109 | 110 | friend bool operator!=(const SystemIterator &a, const SystemIterator &b) { return a.begin != b.begin; }; 111 | 112 | private: 113 | TECSManager &ecs; 114 | TInternalIterator begin; 115 | const TInternalIterator end; 116 | }; 117 | 118 | /** 119 | * System 120 | * A class that takes a ECS as input and creates 121 | * SystemIterators that a user can use to loop over the 122 | * components. 123 | * The lifetime of a System needs to be shorter then 124 | * its underlying ecs as it stores a reference to it. 125 | * @tparam TSystemComponents components to filter on. 126 | */ 127 | template 128 | struct System { 129 | private: 130 | using TSystemIterator = SystemIterator; 131 | public: 132 | constexpr System(TECSManager &ecs, size_t part, size_t totalParts) : ecs(ecs), part(part), totalParts(totalParts), componentRangesMatch(ecs.GetSystemFilterMatch()) { 133 | ValidateInvariant(); 134 | } 135 | 136 | /** 137 | * Returns a iterator to the first value in the system. 138 | * Will match the components and skip over if entity 139 | * does not have the correct components. 140 | * @return TSystemIterator with a references to component data. 141 | */ 142 | [[nodiscard]] TSystemIterator begin() const { 143 | if (!componentRangesMatch) { 144 | return end(); 145 | } 146 | auto end = ecsEnd(); 147 | auto begin = ecsBegin(); 148 | while (begin != end) { 149 | if (ecs.HasGivenComponents(begin)) { 150 | break; 151 | } 152 | begin++; 153 | } 154 | return TSystemIterator(ecs, begin, end); 155 | } 156 | 157 | 158 | /** 159 | * Returns a iterator to end value in the system. 160 | * @return TSystemIterator to end iterator. 161 | */ 162 | [[nodiscard]] TSystemIterator end() const { return TSystemIterator(ecs, ecsEnd(), ecsEnd()); } 163 | 164 | private: 165 | auto ecsEnd() const { 166 | return ecs.end() - endIteratorOffset() - (componentRangesMatch ? ecs.ContainerSize() - 1 - componentRangesMatch->lastSlot : 0); 167 | } 168 | 169 | auto ecsBegin() const { 170 | return ecs.begin() + beginIteratorOffset() + (componentRangesMatch ? componentRangesMatch->firstSlot : 0); 171 | } 172 | 173 | constexpr size_t partSize() const { 174 | return ecs.ContainerSize() / totalParts; 175 | } 176 | 177 | constexpr bool hasRemainder() const { 178 | return ecs.ContainerSize() % totalParts != 0; 179 | } 180 | 181 | size_t endIteratorOffset() const { 182 | if (hasRemainder()) { 183 | if (part == totalParts - 1) { 184 | return 0; 185 | } 186 | } 187 | return ecs.ContainerSize() - (part + 1) * partSize(); 188 | } 189 | 190 | size_t beginIteratorOffset() const { 191 | return part * partSize(); 192 | } 193 | 194 | void ValidateInvariant() const { 195 | if (componentRangesMatch && componentRangesMatch->firstSlot > componentRangesMatch->lastSlot) { 196 | throw std::logic_error("Invariant broken! FirstSlot > LastSlot"); 197 | } 198 | } 199 | 200 | TECSManager &ecs; 201 | size_t part = 0; 202 | size_t totalParts = 1; 203 | std::optional componentRangesMatch{}; 204 | }; 205 | 206 | public: 207 | constexpr ECSManager() = default; 208 | 209 | /** 210 | * AddEntity a new entity to the ECS 211 | * @return EntityID 212 | */ 213 | [[nodiscard]] constexpr inline EntityID AddEntity(); 214 | 215 | /** 216 | * BuildEntity adds a entity and adds in components to the 217 | * new entity. So both AddEntity and Add in one call. 218 | * @return EntityID 219 | */ 220 | template 221 | requires NonVoidArgs 222 | constexpr inline EntityID BuildEntity(TEntityComponents&&... args) { 223 | auto id = AddEntity(); 224 | (Add(id, std::forward(args)), ...); 225 | return id; 226 | } 227 | 228 | /** 229 | * Adds a new component to a entity. 230 | * @tparam TComponent type of the new component. 231 | * @param entityId reference to the entity. 232 | * @param component the data of the component. 233 | */ 234 | template 235 | requires NonVoidArgs && TypeIn 236 | constexpr void Add(const EntityID &entityId, const TComponent &component); 237 | 238 | /** 239 | * Remove a entity from the ECS. 240 | * @param entityId reference to the entity. 241 | */ 242 | constexpr inline void RemoveEntity(const EntityID &entityId); 243 | 244 | /** 245 | * Remove a component from a entity. 246 | * @tparam TComponent type of the component to remove 247 | * @param entityId reference to the entity. 248 | */ 249 | template 250 | requires NonVoidArgs && TypeIn 251 | constexpr void Remove(const EntityID &entityId); 252 | 253 | /** 254 | * Checks if ecs has the given entity 255 | * @param entityId reference to the entity 256 | * @return bool if entity is active. 257 | */ 258 | [[nodiscard]] constexpr inline bool HasEntity(const EntityID &entityId) const; 259 | 260 | /** 261 | * Checks if the given entity has the components. 262 | * @tparam TEntityComponents The type of the components. 263 | * @param entityId reference to the entity 264 | * @return bool if component is active. 265 | */ 266 | template 267 | requires NonVoidArgs 268 | [[nodiscard]] constexpr bool Has(const EntityID &entityId) const; 269 | 270 | /** 271 | * Checks if the ecs has the given component. 272 | * @tparam TEntityComponent The type of the component. 273 | * @return bool if component is present. 274 | * @note This is a static function and does not need a ecs instance. 275 | * @note Recomended to use the ecs::HasTypes<>() function instead, 276 | * to simplify interaction as it works with one or more types and has a 277 | * simpler syntax. 278 | */ 279 | template 280 | requires NonVoidArgs 281 | [[nodiscard]] static constexpr bool HasType() { 282 | return (TypeInPack() || ...); 283 | } 284 | 285 | /** 286 | * Returns a reference to the requested component data. 287 | * @tparam TComponent the type of the component 288 | * @param entityId reference to the entity. 289 | * @return TComponent& reference to the component. 290 | */ 291 | template 292 | requires NonVoidArgs && TypeIn 293 | [[nodiscard]] constexpr TComponent &Get(const EntityID &entityId); 294 | 295 | /** 296 | * A getter to fetch multiple components at once. 297 | * 298 | * @tparam TComponentsRequested 299 | * @param entityId 300 | * @return A tuple with components in the same order as the template arguments. 301 | * can use auto [a, b, c] = GetSeveral(entityId); to unpack. 302 | */ 303 | template 304 | requires NonVoidArgs && (TypeIn && ...) 305 | [[nodiscard]] auto GetSeveral(const EntityID &entityId) { 306 | return std::forward_as_tuple(Get(entityId)...); 307 | } 308 | 309 | /** 310 | * Returns a system which is a list of a set of components. 311 | * @tparam TSystemComponents the list of components in the system. 312 | * @return System the system of components. 313 | */ 314 | template 315 | requires NonVoidArgs && (TypeIn && ...) 316 | [[nodiscard]] constexpr System GetSystem(); 317 | 318 | /** 319 | * Returns a part of the system which is a list of a set of components, 320 | * to be used for splitting the container up for multi threading purposes. 321 | * @tparam TSystemComponents the list of components in the system. 322 | * @param part the part of the system to return. 0 indexed, so 0 is the first part. 323 | * @param totalParts the total number of parts the container is split up into. 324 | * @return System the system of components. 325 | */ 326 | template 327 | requires NonVoidArgs && (TypeIn && ...) 328 | [[nodiscard]] constexpr System GetSystemPart(size_t part, size_t totalParts); 329 | 330 | /** 331 | * Returns number of entities in ECS 332 | * @return size_t 333 | */ 334 | [[nodiscard]] constexpr size_t Size() const; 335 | 336 | /** 337 | * Begin iterator, first element in entities list. 338 | * @return iterator to begin 339 | */ 340 | [[nodiscard]] typename EntitiesSlots::const_iterator begin() const; 341 | 342 | /** 343 | * End iterator, after last element in entities list. 344 | * @return iterator to end 345 | */ 346 | [[nodiscard]] typename EntitiesSlots::const_iterator end() const; 347 | 348 | private: 349 | size_t ContainerSize() const { 350 | return entities.size(); 351 | } 352 | 353 | template 354 | bool HasGivenComponents(const auto& it) const { 355 | return it->active && Has(it->id); 356 | } 357 | 358 | template 359 | void UpdateComponentRange(const EntityID &entityId) { 360 | if (!HasInternal(entityId)) { 361 | throw std::logic_error("Not a valid id!"); 362 | } 363 | auto &componentRange = std::get>(componentRanges); 364 | if (!componentRange.componentPresent) { 365 | componentRange.componentPresent = true; 366 | componentRange.firstSlot = std::min(entityId.GetId(), componentRange.firstSlot); 367 | componentRange.lastSlot = std::max(entityId.GetId(), componentRange.lastSlot); 368 | } else { 369 | componentRange.lastSlot = entityId.GetId(); 370 | } 371 | } 372 | 373 | template 374 | std::optional GetSystemFilterMatch() { 375 | bool found = false; 376 | size_t firstSlot = 0; 377 | size_t lastSlot = SIZE_MAX; 378 | 379 | std::apply([&] (TComponentRange&&... args) { 380 | (((ComponentTypeInPack() && args.componentPresent) ? found = true : 0), ...); 381 | (((ComponentTypeInPack() && args.componentPresent) ? firstSlot = std::max(args.firstSlot, firstSlot) : 0), ...); 382 | (((ComponentTypeInPack() && args.componentPresent) ? lastSlot = std::min(args.lastSlot, lastSlot) : 0), ...); 383 | }, componentRanges); 384 | if (!found) { 385 | return std::nullopt; 386 | } 387 | return ComponentRangesMatch{firstSlot, lastSlot}; 388 | } 389 | 390 | template TEntityComponent> 391 | [[nodiscard]] bool HasInternal(const EntityID &entityId) const { 392 | return std::get>(entities[entityId.GetId()].activeComponents).active; 393 | } 394 | 395 | template 396 | [[nodiscard]] AvailableComponent &GetComponent(const EntityID &entityId) { 397 | return std::get>(GetEntity(entityId.GetId()).activeComponents); 398 | } 399 | 400 | [[nodiscard]] inline Entity &GetEntity(size_t index) { 401 | ValidateID(index); 402 | return entities[index]; 403 | } 404 | 405 | template 406 | [[nodiscard]] const AvailableComponent &ReadComponent(const EntityID &entityId) const { 407 | return std::get>(entities[entityId.GetId()].activeComponents); 408 | } 409 | 410 | template TComponent> 411 | [[nodiscard]] TComponent &GetComponentData(const EntityID &entityId) { 412 | ValidateID(entityId.GetId()); 413 | return GetComponentDataArray()[entityId.GetId()]; 414 | } 415 | 416 | template TComponent> 417 | [[nodiscard]] ComponentArray &GetComponentDataArray() { 418 | return std::get>(componentArrays); 419 | } 420 | 421 | inline void ValidateID(size_t index) const { 422 | if (index >= endSlot) { 423 | throw std::out_of_range("Accessing outside of endSlot!"); 424 | } 425 | } 426 | 427 | inline void ValidateEntityID(EntityID id) const { 428 | if (not id) { 429 | throw std::logic_error("ID not initialized!"); 430 | } 431 | } 432 | 433 | size_t GetFirstEmptySlot() { 434 | size_t slot = 0; 435 | while (slot < endSlot && entities[slot].active) { 436 | slot++; 437 | } 438 | if (slot == endSlot) { 439 | endSlot++; 440 | } 441 | return slot; 442 | } 443 | 444 | [[nodiscard]] size_t GetLastSlot() const { 445 | if (endSlot == 0) { 446 | return 0; 447 | } 448 | return endSlot - 1; 449 | } 450 | 451 | size_t endSlot = 0; 452 | size_t nrEntities = 0; 453 | EntitiesSlots entities; 454 | ComponentMatrix componentArrays{}; 455 | ComponentRanges componentRanges{}; 456 | }; 457 | 458 | template 459 | requires NonVoidArgs && IsBasicType 460 | constexpr EntityID ECSManager::AddEntity() { 461 | auto slot = GetFirstEmptySlot(); 462 | if (slot == entities.size()) { 463 | entities.push_back({.id=EntityID(slot)}); 464 | std::apply([](auto &&...args) { ((PushToVector(args)), ...); }, componentArrays); 465 | } 466 | auto &entity = GetEntity(slot); 467 | entity.active = true; 468 | std::apply([](auto &&...args) { ((args.active = false), ...); }, entity.activeComponents); 469 | nrEntities++; 470 | if constexpr ((std::is_same::value || ...)) { 471 | Add(entity.id, entity.id); 472 | } 473 | return entity.id; 474 | } 475 | 476 | template 477 | requires NonVoidArgs && IsBasicType 478 | template 479 | requires NonVoidArgs && TypeIn 480 | constexpr void ECSManager::Add(const EntityID &entityId, const TComponent &component) { 481 | ValidateEntityID(entityId); 482 | auto &isActive = GetComponent(entityId).active; 483 | if (isActive) { 484 | throw std::logic_error("Component already added!"); 485 | } 486 | isActive = true; 487 | GetComponentData(entityId) = component; 488 | UpdateComponentRange(entityId); 489 | } 490 | 491 | template 492 | requires NonVoidArgs && IsBasicType 493 | constexpr void ECSManager::RemoveEntity(const EntityID &entityId) { 494 | ValidateEntityID(entityId); 495 | auto &entity = GetEntity(entityId.GetId()); 496 | if (!entity.active) { 497 | throw std::logic_error("Entity not active!"); 498 | } 499 | entity.active = false; 500 | if (GetLastSlot() == entity.id.GetId()) { 501 | endSlot--; 502 | } 503 | nrEntities--; 504 | } 505 | 506 | template 507 | requires NonVoidArgs && IsBasicType 508 | template 509 | requires NonVoidArgs && TypeIn 510 | constexpr void ECSManager::Remove(const EntityID &entityId) { 511 | ValidateEntityID(entityId); 512 | auto &isActive = GetComponent(entityId).active; 513 | if (!isActive) { 514 | throw std::logic_error("Component not active!"); 515 | } 516 | isActive = false; 517 | } 518 | 519 | template 520 | requires NonVoidArgs && IsBasicType 521 | constexpr bool ECSManager::HasEntity(const EntityID &entityId) const { 522 | ValidateEntityID(entityId); 523 | if (entityId.GetId() >= entities.size()) { 524 | throw std::out_of_range("Trying to access out of bounds!"); 525 | } 526 | return entities[entityId.GetId()].active; 527 | } 528 | 529 | template 530 | requires NonVoidArgs && IsBasicType 531 | template 532 | requires NonVoidArgs 533 | constexpr bool ECSManager::Has(const EntityID &entityId) const { 534 | ValidateEntityID(entityId); 535 | return (HasInternal(entityId) && ...); 536 | } 537 | 538 | template 539 | requires NonVoidArgs && IsBasicType 540 | template 541 | requires NonVoidArgs && TypeIn 542 | constexpr TComponent &ECSManager::Get(const EntityID &entityId) { 543 | ValidateEntityID(entityId); 544 | if (!ReadComponent(entityId).active) { 545 | throw std::invalid_argument("Bad access, component not present on this entity."); 546 | } 547 | return GetComponentData(entityId); 548 | } 549 | 550 | template 551 | requires NonVoidArgs && IsBasicType 552 | template 553 | requires NonVoidArgs && (TypeIn && ...) 554 | constexpr typename ECSManager::template System ECSManager::GetSystem() { 555 | return GetSystemPart(0, 1); 556 | } 557 | 558 | template 559 | requires NonVoidArgs && IsBasicType 560 | template 561 | requires NonVoidArgs && (TypeIn && ...) 562 | constexpr typename ECSManager::template System ECSManager::GetSystemPart(size_t part, size_t totalParts) { 563 | return System(*this, part, totalParts); 564 | } 565 | 566 | template 567 | requires NonVoidArgs && IsBasicType 568 | constexpr size_t ECSManager::Size() const { 569 | return nrEntities; 570 | } 571 | 572 | template 573 | requires NonVoidArgs && IsBasicType 574 | [[nodiscard]] typename ECSManager::EntitiesSlots::const_iterator 575 | ECSManager::begin() const { 576 | return entities.begin(); 577 | } 578 | 579 | template 580 | requires NonVoidArgs && IsBasicType 581 | [[nodiscard]] typename ECSManager::EntitiesSlots::const_iterator 582 | ECSManager::end() const { 583 | return entities.begin() + endSlot; 584 | } 585 | 586 | template 587 | constexpr bool HasTypes() { 588 | return (TECSManager::template HasType() && ...); 589 | } 590 | }// namespace ecs 591 | -------------------------------------------------------------------------------- /include/ecs-cpp/EcsEffects.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2021-05-12. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "EcsCpp.h" 8 | #include "EntityID.h" 9 | #include "EcsUtil.h" 10 | 11 | 12 | namespace ecs { 13 | template 14 | class Attribute { 15 | public: 16 | Attribute(T t) : Base(t), PostEffects(t) {} 17 | T& Get() { 18 | return PostEffects; 19 | } 20 | 21 | /** 22 | * Internal use only 23 | */ 24 | void Reset() { 25 | PostEffects = Base; 26 | } 27 | private: 28 | T PostEffects; 29 | T Base; 30 | }; 31 | 32 | template 33 | struct Attributes { 34 | using AttributeList = std::tuple; 35 | }; 36 | 37 | 38 | /** 39 | * A effect is a temporary modification of a attribute, which is described in ECS. 40 | * An effect can be applied to one or more entities, and will modify the attribute of the entity. 41 | * The effect can be removed, and the attribute will return to its original value. 42 | * 43 | * The EcsEffect is a addition to EcsCpp, so EcsCpp does not depend on it and 44 | * to use it, you would only access your entities attributes through the 45 | * EcsEffect system instead of the regular ECSManager. 46 | * 47 | * State is a regular value, and the attribute is a value tracked in the AttributesEffectsManager. 48 | */ 49 | template ... TEffects> 50 | class AttributesEffectsManager { 51 | public: 52 | using EffectID = size_t; 53 | template TEffect> 54 | struct EffectElement { 55 | EffectID Id = 0; 56 | TEffect Effect; 57 | }; 58 | template TEffect> 59 | using EffectList = std::vector>; 60 | 61 | /** 62 | * Add a effect to a entity. 63 | * @tparam TAddedEffects 64 | * @param entity 65 | * @param effects 66 | */ 67 | template ... TAddedEffects> 68 | void Add(EntityID entity, TAddedEffects&&... effects) { 69 | for (auto& affectedEntity : AffectedEntities) { 70 | if (affectedEntity.Entity == entity) { 71 | (std::get>(affectedEntity.Effects).push_back(EffectElement{EffectIdCounter++, effects}), ...); 72 | return; 73 | } 74 | } 75 | AffectedEntities.push_back({entity, EffectMatrix{}, AttributeBase}); 76 | (std::get>(AffectedEntities.back().Effects).push_back(EffectElement{EffectIdCounter++, effects}), ...); 77 | } 78 | 79 | /** 80 | * Remove all effects from a entity and resets its attributes to base. 81 | */ 82 | void Reset(EntityID entity) { 83 | auto it = std::find_if(AffectedEntities.begin(), AffectedEntities.end(), [&entity](auto&& entities){ 84 | return entities.Entity == entity; 85 | }); 86 | if (it == AffectedEntities.end()) { 87 | return; 88 | } 89 | AffectedEntities.erase(it); 90 | } 91 | 92 | /** 93 | * Modify a attribute of a entity. 94 | */ 95 | template ... TModifiedAttributes> 96 | void Modify(EntityID entity, TModifiedAttributes&&... attributes) { 97 | if constexpr (sizeof...(TModifiedAttributes) == 0) { 98 | static_assert("Not allowed to pass 0 parameters to modify."); 99 | } 100 | auto it = std::find_if(AffectedEntities.begin(), AffectedEntities.end(), [&entity](auto&& entities){ 101 | return entities.Entity == entity; 102 | }); 103 | if (it == AffectedEntities.end()){ 104 | AffectedEntities.push_back({entity, EffectMatrix{}, AttributeBase}); 105 | it = AffectedEntities.end() - 1; 106 | } 107 | ((std::get(it->AttributeBase) = attributes), ...); 108 | } 109 | 110 | /** 111 | * Get a attribute of a entity. 112 | */ 113 | template TAttribute> 114 | TAttribute Get(EntityID entity) const { 115 | auto it = std::find_if(AffectedEntities.begin(), AffectedEntities.end(), [&entity](auto&& entities){ 116 | return entities.Entity == entity; 117 | }); 118 | if (it == AffectedEntities.end()){ 119 | return GetBase(); 120 | } 121 | return it->template Apply(); 122 | } 123 | 124 | /** 125 | * Get a list of all effects on the given attribute for the entity. 126 | */ 127 | template TEffect> 128 | const EffectList& GetEffects(EntityID entity) { 129 | auto it = std::find_if(AffectedEntities.begin(), AffectedEntities.end(), [&entity](auto&& entities){ 130 | return entities.Entity == entity; 131 | }); 132 | if (it == AffectedEntities.end()) { 133 | static EffectList empty; 134 | return empty; 135 | } 136 | return std::get>(it->Effects); 137 | } 138 | 139 | /** 140 | * Removes a list of effects from a entity. 141 | */ 142 | template TEffect> 143 | void Remove(EntityID entity, const EffectList& effects) { 144 | auto it = std::find_if(AffectedEntities.begin(), AffectedEntities.end(), [&entity](auto&& entities){ 145 | return entities.Entity == entity; 146 | }); 147 | if (it == AffectedEntities.end()) { 148 | return; 149 | } 150 | 151 | auto& effectList = std::get>(it->Effects); 152 | for (auto& effect : effects) { 153 | auto effectIt = std::find_if(effectList.begin(), effectList.end(), [&effect](auto& effectElement){ 154 | return effectElement.Id == effect.Id; 155 | }); 156 | if (effectIt != effectList.end()) { 157 | effectList.erase(effectIt); 158 | } 159 | } 160 | } 161 | private: 162 | template 163 | const TAttribute& GetBase() const { 164 | return std::get(AttributeBase); 165 | } 166 | using EffectMatrix = std::tuple...>; 167 | struct AffectedEntity { 168 | EntityID Entity; 169 | EffectMatrix Effects; 170 | TAttributes::AttributeList AttributeBase; 171 | 172 | template 173 | TAttribute Apply() const { 174 | auto base = std::get(AttributeBase); 175 | std::apply([&](auto&... effects) { 176 | (( 177 | std::for_each(effects.begin(), effects.end(), [&](auto& effectElement) { 178 | auto& effect = effectElement.Effect; 179 | using EffectAttribute = typename std::remove_reference_t::Attribute; 180 | if constexpr (std::is_same_v) { 181 | effect.Apply(base); 182 | } 183 | }) 184 | ), ...); 185 | }, Effects); 186 | return base; 187 | } 188 | }; 189 | 190 | TAttributes::AttributeList AttributeBase; 191 | std::vector AffectedEntities; 192 | EffectID EffectIdCounter = 0; 193 | }; 194 | }// namespace ecs 195 | -------------------------------------------------------------------------------- /include/ecs-cpp/EcsUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-12-12. 3 | // 4 | 5 | #pragma once 6 | 7 | template 8 | concept TypeIn = (std::same_as, TypesToCheckAgainst> || ...); 9 | 10 | template 11 | constexpr bool TypeInPack() { 12 | return (std::same_as, TypesToCheckAgainst> || ...); 13 | } 14 | 15 | template 16 | constexpr bool ComponentTypeInPack() { 17 | return (std::same_as::TComponentRange, TypesToCheckAgainst> || ...); 18 | } 19 | 20 | template 21 | concept NonVoidArgs = sizeof...(Args) > 0; 22 | 23 | template 24 | constexpr bool Contains(std::tuple) { 25 | return std::disjunction_v...>; 26 | } 27 | 28 | template 29 | concept ContainedIn = Contains(typename Attributes::AttributeList{}); 30 | 31 | template 32 | concept IsEffect = 33 | requires(TEffect t, TEffect::Attribute& a, const Attributes::AttributeList& attributeList) { 34 | { std::get(attributeList) }; 35 | { t.Apply(a) }; 36 | } && Contains(typename Attributes::AttributeList{}); 37 | 38 | template 39 | concept IsAttributes = requires(typename TAttributes::AttributeList attributeList) { 40 | { std::get<0>(attributeList) }; 41 | }; 42 | 43 | template 44 | concept IsBasicType = (( 45 | std::default_initializable && 46 | not std::is_pointer_v && 47 | not std::is_reference_v && 48 | not std::is_const_v && 49 | not std::is_volatile_v) && 50 | ...); 51 | 52 | template 53 | void PushToVector(std::vector& vector) { 54 | vector.push_back(T{}); 55 | } 56 | -------------------------------------------------------------------------------- /include/ecs-cpp/EntityID.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-12-12. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace ecs { 10 | /** 11 | * EntityID 12 | * Id reference to a entity. 13 | */ 14 | class EntityID { 15 | public: 16 | using ID = size_t; 17 | 18 | constexpr EntityID() = default; 19 | 20 | constexpr EntityID(ID id) 21 | : id(id) { 22 | } 23 | 24 | [[nodiscard]] constexpr const EntityID::ID &GetId() const { 25 | return id; 26 | } 27 | 28 | friend constexpr bool operator==(const EntityID &a, const EntityID &b) { return a.id == b.id; }; 29 | 30 | constexpr operator bool() const { return id != SIZE_MAX; } 31 | 32 | private: 33 | ID id = SIZE_MAX; 34 | }; 35 | 36 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | find_package(GTest) 3 | 4 | enable_testing() 5 | 6 | add_executable(${PROJECT_NAME}_tests ECSTests.cpp) 7 | target_link_libraries(${PROJECT_NAME}_tests GTest::gtest GTest::gtest_main ecs-cpp) 8 | target_include_directories(${PROJECT_NAME}_tests PUBLIC ".") 9 | 10 | include(GoogleTest) 11 | gtest_discover_tests(${PROJECT_NAME}_tests) -------------------------------------------------------------------------------- /tests/ECSTests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefan Annell on 2023-01-01. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | TEST(ECS, GetLastSlot) { 11 | ecs::ECSManager ecs; 12 | auto entity = ecs.AddEntity(); 13 | ecs.RemoveEntity(entity); 14 | } 15 | 16 | TEST(ECS, AddEntity) { 17 | ecs::ECSManager ecs; 18 | 19 | auto entity = ecs.AddEntity(); 20 | auto entity2 = ecs.AddEntity(); 21 | 22 | ASSERT_EQ(entity.GetId(), 0); 23 | ASSERT_EQ(entity2.GetId(), 1); 24 | } 25 | 26 | TEST(ECS, AddEntityAndComponent) { 27 | ecs::ECSManager ecs; 28 | 29 | auto entity = ecs.BuildEntity(1, std::string("hej")); 30 | auto entity2 = ecs.AddEntity(); 31 | 32 | ASSERT_EQ(entity.GetId(), 0); 33 | ASSERT_EQ(entity2.GetId(), 1); 34 | 35 | ASSERT_TRUE(ecs.Has(entity)); 36 | ASSERT_TRUE(ecs.Has(entity)); 37 | 38 | ASSERT_FALSE(ecs.Has(entity2)); 39 | ASSERT_FALSE(ecs.Has(entity2)); 40 | ASSERT_EQ(ecs.Get(entity), 1); 41 | ASSERT_EQ(ecs.Get(entity), "hej"); 42 | } 43 | 44 | TEST(ECS, NotSharedSpace) { 45 | ecs::ECSManager ecs; 46 | 47 | ecs::EntityID entity = ecs.AddEntity(); 48 | ecs::EntityID entity2 = ecs.AddEntity(); 49 | ecs.Add(entity, std::string("Hej")); 50 | ecs.Add(entity2, 5); 51 | 52 | 53 | ASSERT_TRUE(ecs.Has(entity)); 54 | ASSERT_FALSE(ecs.Has(entity)); 55 | 56 | ASSERT_FALSE(ecs.Has(entity2)); 57 | ASSERT_TRUE(ecs.Has(entity2)); 58 | } 59 | 60 | TEST(ECS, SharedSpace) { 61 | ecs::ECSManager ecs; 62 | 63 | ecs::EntityID entity = ecs.AddEntity(); 64 | ecs.Add(entity, std::string("Hej")); 65 | ecs.Add(entity, 5); 66 | 67 | ASSERT_TRUE(ecs.Has(entity)); 68 | ASSERT_TRUE(ecs.Has(entity)); 69 | } 70 | 71 | TEST(ECS, ReadValue) { 72 | ecs::ECSManager ecs; 73 | 74 | ecs::EntityID entity = ecs.AddEntity(); 75 | 76 | ASSERT_FALSE(ecs.Has(entity)); 77 | EXPECT_THROW(auto ret = ecs.Get(entity), std::invalid_argument); 78 | 79 | ecs.Add(entity, 5); 80 | ASSERT_TRUE(ecs.Has(entity)); 81 | ASSERT_EQ(ecs.Get(entity), 5); 82 | } 83 | 84 | TEST(ECS, AddTwiceError) { 85 | ecs::ECSManager ecs; 86 | ecs::EntityID entity = ecs.AddEntity(); 87 | 88 | ecs.Add(entity, 5); 89 | EXPECT_THROW(ecs.Add(entity, 5), std::logic_error); 90 | } 91 | 92 | TEST(ECS, ReadValueSeveralEntities) { 93 | ecs::ECSManager ecs; 94 | 95 | ecs::EntityID entity = ecs.AddEntity(); 96 | ecs::EntityID entity2 = ecs.AddEntity(); 97 | 98 | ASSERT_FALSE(ecs.Has(entity)); 99 | ecs.Add(entity, 5); 100 | ASSERT_TRUE(ecs.Has(entity)); 101 | ASSERT_EQ(ecs.Get(entity), 5); 102 | 103 | ASSERT_FALSE(ecs.Has(entity2)); 104 | ecs.Add(entity2, 42); 105 | ASSERT_TRUE(ecs.Has(entity2)); 106 | ASSERT_EQ(ecs.Get(entity2), 42); 107 | } 108 | 109 | TEST(ECS, ReadValueSeveralEntitiesAndComponents) { 110 | ecs::ECSManager ecs; 111 | 112 | ecs::EntityID entity = ecs.AddEntity(); 113 | ecs::EntityID entity2 = ecs.AddEntity(); 114 | 115 | ASSERT_FALSE(ecs.Has(entity)); 116 | ASSERT_FALSE(ecs.Has(entity)); 117 | ecs.Add(entity, 5); 118 | ASSERT_FALSE(ecs.Has(entity)); 119 | ASSERT_TRUE(ecs.Has(entity)); 120 | ASSERT_EQ(ecs.Get(entity), 5); 121 | 122 | ASSERT_FALSE(ecs.Has(entity2)); 123 | ASSERT_FALSE(ecs.Has(entity2)); 124 | ecs.Add(entity2, 42); 125 | ASSERT_FALSE(ecs.Has(entity2)); 126 | ASSERT_TRUE(ecs.Has(entity2)); 127 | ASSERT_EQ(ecs.Get(entity2), 42); 128 | 129 | ASSERT_FALSE(ecs.Has(entity)); 130 | ecs.Add(entity, std::string("Hej")); 131 | ASSERT_TRUE(ecs.Has(entity)); 132 | ASSERT_TRUE(ecs.Has(entity)); 133 | ASSERT_EQ(ecs.Get(entity), 5); 134 | ASSERT_EQ(ecs.Get(entity), "Hej"); 135 | 136 | ASSERT_FALSE(ecs.Has(entity2)); 137 | ecs.Add(entity2, std::string("World")); 138 | ASSERT_TRUE(ecs.Has(entity2)); 139 | ASSERT_TRUE(ecs.Has(entity2)); 140 | ASSERT_EQ(ecs.Get(entity2), 42); 141 | ASSERT_EQ(ecs.Get(entity2), "World"); 142 | ASSERT_EQ(ecs.Get(entity), 5); 143 | ASSERT_EQ(ecs.Get(entity), "Hej"); 144 | } 145 | 146 | TEST(ECS, InvalidEntityHasComponent) { 147 | ecs::ECSManager ecs; 148 | 149 | ecs::EntityID entity = ecs.AddEntity(); 150 | ecs.Add(entity, 5); 151 | ASSERT_TRUE(ecs.Has(entity)); 152 | ASSERT_EQ(ecs.Get(entity), 5); 153 | 154 | auto fakeEntity = ecs::EntityID(24); 155 | EXPECT_FALSE(ecs.Has(fakeEntity)); 156 | EXPECT_THROW(auto output = ecs.Get(fakeEntity), std::invalid_argument); 157 | } 158 | 159 | TEST(ECS, InvalidEntityHasEntity) { 160 | ecs::ECSManager ecs; 161 | 162 | ecs::EntityID entity = ecs.AddEntity(); 163 | ecs.Add(entity, 5); 164 | ASSERT_TRUE(ecs.Has(entity)); 165 | ASSERT_EQ(ecs.Get(entity), 5); 166 | ASSERT_TRUE(ecs.HasEntity(entity)); 167 | 168 | auto fakeEntity = ecs::EntityID(24); 169 | EXPECT_THROW(auto ret = ecs.HasEntity(fakeEntity), std::out_of_range); 170 | 171 | auto fakeEntity2 = ecs::EntityID(99999); 172 | EXPECT_THROW(auto ret = ecs.HasEntity(fakeEntity2), std::out_of_range); 173 | } 174 | 175 | TEST(ECS, CheckLastSlot) { 176 | ecs::ECSManager ecs; 177 | 178 | ecs::EntityID entity = ecs.AddEntity(); 179 | ASSERT_TRUE(ecs.HasEntity(entity)); 180 | ASSERT_EQ(entity.GetId(), 0); 181 | auto fakeEntity = ecs::EntityID(1); 182 | ASSERT_EQ(fakeEntity.GetId(), 1); 183 | EXPECT_THROW(auto ret = ecs.HasEntity(fakeEntity), std::out_of_range); 184 | 185 | auto fakeEntity2 = ecs::EntityID(-1); 186 | ASSERT_EQ(fakeEntity2.GetId(), -1); 187 | EXPECT_THROW(auto ret = ecs.HasEntity(fakeEntity2), std::logic_error); 188 | } 189 | 190 | TEST(ECS, RemoveEntity) { 191 | ecs::ECSManager ecs; 192 | 193 | ecs::EntityID entity = ecs.AddEntity(); 194 | ASSERT_TRUE(ecs.HasEntity(entity)); 195 | ecs.RemoveEntity(entity); 196 | ASSERT_FALSE(ecs.HasEntity(entity)); 197 | } 198 | 199 | TEST(ECS, ReclaimId) { 200 | ecs::ECSManager ecs; 201 | 202 | ecs::EntityID entity = ecs.AddEntity(); 203 | ASSERT_TRUE(ecs.HasEntity(entity)); 204 | ASSERT_EQ(entity.GetId(), 0); 205 | ecs.RemoveEntity(entity); 206 | ASSERT_FALSE(ecs.HasEntity(entity)); 207 | 208 | ecs::EntityID entity2 = ecs.AddEntity(); 209 | ASSERT_EQ(entity2.GetId(), 0); 210 | ASSERT_TRUE(ecs.HasEntity(entity2)); 211 | } 212 | 213 | TEST(ECS, RemoveCleanupComponents) { 214 | ecs::ECSManager ecs; 215 | 216 | ASSERT_EQ(ecs.Size(), 0); 217 | ecs::EntityID entity = ecs.AddEntity(); 218 | ASSERT_EQ(ecs.Size(), 1); 219 | ASSERT_TRUE(ecs.HasEntity(entity)); 220 | ecs.Add(entity, 5); 221 | ASSERT_TRUE(ecs.Has(entity)); 222 | ecs.RemoveEntity(entity); 223 | ASSERT_EQ(ecs.Size(), 0); 224 | ASSERT_FALSE(ecs.HasEntity(entity)); 225 | 226 | ASSERT_THROW(ecs.RemoveEntity(entity), std::logic_error); 227 | ASSERT_EQ(ecs.Size(), 0); 228 | 229 | ecs::EntityID entity2 = ecs.AddEntity(); 230 | ASSERT_EQ(ecs.Size(), 1); 231 | ASSERT_EQ(entity2.GetId(), 0); 232 | ASSERT_TRUE(ecs.HasEntity(entity2)); 233 | ASSERT_FALSE(ecs.Has(entity2)); 234 | } 235 | 236 | TEST(ECS, RemoveComponent) { 237 | ecs::ECSManager ecs; 238 | 239 | auto entity = ecs.AddEntity(); 240 | ecs.Add(entity, 5); 241 | ASSERT_TRUE(ecs.HasEntity(entity)); 242 | ASSERT_TRUE(ecs.Has(entity)); 243 | ecs.Remove(entity); 244 | ASSERT_TRUE(ecs.HasEntity(entity)); 245 | ASSERT_FALSE(ecs.Has(entity)); 246 | 247 | ASSERT_THROW(ecs.Remove(entity), std::logic_error); 248 | } 249 | 250 | TEST(ECS, LoopEntities) { 251 | ecs::ECSManager ecs; 252 | { 253 | int callCount = 0; 254 | for (auto &entity: ecs) { 255 | callCount++; 256 | } 257 | ASSERT_EQ(callCount, 0); 258 | } 259 | 260 | { 261 | auto entity = ecs.AddEntity(); 262 | ecs.Add(entity, 5); 263 | int callCount = 0; 264 | for (auto &e: ecs) { 265 | callCount++; 266 | auto [i, str] = e.activeComponents; 267 | EXPECT_EQ(e.id, entity); 268 | EXPECT_FALSE(str.active); 269 | EXPECT_TRUE(i.active); 270 | } 271 | ASSERT_EQ(callCount, 1); 272 | } 273 | { 274 | auto entity = ecs.AddEntity(); 275 | ecs.Add(entity, 2); 276 | ecs.Add(entity, std::string("hej")); 277 | 278 | int callCount = 0; 279 | for (auto &e: ecs) { 280 | callCount++; 281 | if (e.id == entity) { 282 | auto [str, i] = e.activeComponents; 283 | EXPECT_TRUE(str.active); 284 | EXPECT_TRUE(i.active); 285 | auto result = ecs.Has(e.id); 286 | EXPECT_TRUE(result); 287 | } 288 | } 289 | ASSERT_EQ(callCount, 2); 290 | ecs.RemoveEntity(entity); 291 | callCount = 0; 292 | for (auto &e: ecs) { 293 | callCount++; 294 | } 295 | ASSERT_EQ(callCount, 1); 296 | ASSERT_FALSE(ecs.HasEntity(entity)); 297 | } 298 | } 299 | 300 | TEST(ECS, LoopOnceWithFilter) { 301 | ecs::ECSManager ecs; 302 | 303 | auto entity = ecs.AddEntity(); 304 | ecs.Add(entity, 5); 305 | ecs.Add(entity, "string"); 306 | int count = 0; 307 | for (auto [val1, val2]: ecs.GetSystem()) { 308 | ASSERT_EQ(val1, 5); 309 | ASSERT_EQ(val2, "string"); 310 | count++; 311 | } 312 | ASSERT_EQ(count, 1); 313 | } 314 | 315 | TEST(ECS, LoopTwiceWithFilter) { 316 | ecs::ECSManager ecs; 317 | 318 | ecs.BuildEntity(5, std::string("string")); 319 | ecs.BuildEntity(5, std::string("string")); 320 | int count = 0; 321 | for (auto [val1, val2]: ecs.GetSystem()) { 322 | ASSERT_EQ(val1, 5); 323 | ASSERT_EQ(val2, "string"); 324 | count++; 325 | } 326 | ASSERT_EQ(count, 2); 327 | } 328 | 329 | TEST(ECS, LoopMultipleWithFilter) { 330 | ecs::ECSManager ecs; 331 | { 332 | auto entity = ecs.AddEntity(); 333 | ecs.Add(entity, 5); 334 | ecs.Add(entity, "one"); 335 | } 336 | { 337 | auto entity = ecs.AddEntity(); 338 | ecs.Add(entity, "two"); 339 | } 340 | { 341 | auto entity = ecs.AddEntity(); 342 | ecs.Add(entity, 6); 343 | } 344 | { 345 | auto entity = ecs.AddEntity(); 346 | ecs.Add(entity, 7); 347 | ecs.Add(entity, "three"); 348 | } 349 | 350 | { 351 | int count = 0; 352 | for (auto [val1, val2]: ecs.GetSystem()) { 353 | count++; 354 | } 355 | ASSERT_EQ(count, 2); 356 | } 357 | 358 | { 359 | int count = 0; 360 | for (auto [val]: ecs.GetSystem()) { 361 | count++; 362 | } 363 | ASSERT_EQ(count, 3); 364 | } 365 | 366 | { 367 | int count = 0; 368 | for (auto [val]: ecs.GetSystem()) { 369 | count++; 370 | } 371 | ASSERT_EQ(count, 3); 372 | } 373 | } 374 | 375 | TEST(ECS, RemoveEntityAndLoop) { 376 | using ECSContainer = ecs::ECSManager; 377 | ECSContainer ecs; 378 | auto entity = ecs.AddEntity(); 379 | ecs.Add(entity, 5); 380 | ecs.Add(entity, "one"); 381 | auto entity2 = ecs.AddEntity(); 382 | ecs.Add(entity2, "two"); 383 | auto entity3 = ecs.AddEntity(); 384 | ecs.Add(entity3, 6); 385 | 386 | { 387 | int count = 0; 388 | for (auto [val]: ecs.GetSystem()) { 389 | count++; 390 | } 391 | ASSERT_EQ(count, 2); 392 | 393 | auto system = ecs.GetSystem(); 394 | std::for_each(system.begin(), system.end(), [&](const auto &it) { 395 | auto [val] = it; 396 | count++; 397 | }); 398 | ASSERT_EQ(count, 4); 399 | } 400 | 401 | ecs.Remove(entity2); 402 | { 403 | int count = 0; 404 | for (auto [val]: ecs.GetSystem()) { 405 | count++; 406 | } 407 | ASSERT_EQ(count, 1); 408 | } 409 | 410 | ecs.Remove(entity); 411 | { 412 | int count = 0; 413 | for (auto [val]: ecs.GetSystem()) { 414 | count++; 415 | } 416 | ASSERT_EQ(count, 1); 417 | } 418 | 419 | ecs.Remove(entity3); 420 | { 421 | int count = 0; 422 | for (auto [val]: ecs.GetSystem()) { 423 | count++; 424 | } 425 | ASSERT_EQ(count, 0); 426 | } 427 | 428 | ecs.Remove(entity); 429 | { 430 | int count = 0; 431 | for (auto [val]: ecs.GetSystem()) { 432 | count++; 433 | } 434 | ASSERT_EQ(count, 0); 435 | } 436 | } 437 | 438 | TEST(ECS, ReadmeShowcase) { 439 | ecs::ECSManager ecs; 440 | auto entity1 = ecs.AddEntity(); 441 | auto entity2 = ecs.AddEntity(); 442 | auto entity3 = ecs.AddEntity(); 443 | 444 | // Fill up container with components 445 | ecs.Add(entity1, 1); 446 | ecs.Add(entity1, std::string("Hello")); 447 | //ecs.AddEntity(entity1, 5.0f); // No float component on entity1 448 | 449 | ecs.Add(entity2, 2); 450 | ecs.Add(entity2, std::string("World")); 451 | ecs.Add(entity2, 5.0f); 452 | 453 | ecs.Add(entity3, 3); 454 | //ecs.AddEntity(entity3, "!"); // No string component on entity3 455 | ecs.Add(entity3, 5.0f); 456 | 457 | // Will match entity1 and entity2 458 | std::string output; 459 | for (auto [strVal, intVal]: ecs.GetSystem()) { 460 | output += strVal + " - " + std::to_string(intVal) + " "; 461 | } 462 | ASSERT_EQ(output, "Hello - 1 World - 2 "); 463 | 464 | float fsum = 0; 465 | int isum = 0; 466 | // Will match entity2 and entity3 467 | for (auto [intVal, floatVal]: ecs.GetSystem()) { 468 | isum += intVal; 469 | fsum += floatVal; 470 | } 471 | ASSERT_EQ(fsum, 10.0f); 472 | ASSERT_EQ(isum, 5); 473 | } 474 | 475 | TEST(ECS, FindInSystem) { 476 | ecs::ECSManager ecs; 477 | auto entity1 = ecs.AddEntity(); 478 | auto entity2 = ecs.AddEntity(); 479 | auto entity3 = ecs.AddEntity(); 480 | 481 | // Fill up container with components 482 | ecs.Add(entity1, 1); 483 | ecs.Add(entity1, std::string("Hello")); 484 | //ecs.AddEntity(entity1, 5.0f); // No float component on entity1 485 | 486 | ecs.Add(entity2, 2); 487 | ecs.Add(entity2, std::string("World")); 488 | ecs.Add(entity2, 5.0f); 489 | 490 | ecs.Add(entity3, 3); 491 | ecs.Add(entity3, std::string("World")); 492 | ecs.Add(entity3, 5.0f); 493 | 494 | auto system = ecs.GetSystem(); 495 | auto isCorrectWorld = [](const auto &it) { 496 | auto [intVal, stringVal] = it; 497 | return stringVal == "World" && intVal == 3; 498 | }; 499 | 500 | /*auto it = std::find_if(system.begin(), system.end(), isCorrectWorld); 501 | ASSERT_TRUE(it != system.end()); 502 | auto [intVal, strVal] = *it; 503 | ASSERT_EQ(intVal, 3);*/ 504 | } 505 | 506 | TEST(ECS, DefaultConstrutableConcept) { 507 | struct DefaultConstrutable { 508 | int val = 0; 509 | float bla = 0.0f; 510 | }; 511 | 512 | struct NotDefaultConstrutable { 513 | NotDefaultConstrutable() = delete; 514 | 515 | NotDefaultConstrutable(int val, float bla) : val(val), bla(bla) {} 516 | 517 | int val = 0; 518 | float bla = 0.0f; 519 | }; 520 | 521 | static_assert(std::default_initializable); 522 | static_assert(not std::default_initializable); 523 | } 524 | 525 | TEST(ECS, FillHoleTest) { 526 | ecs::ECSManager ecs; 527 | auto e1 = ecs.AddEntity(); 528 | auto e2 = ecs.AddEntity(); 529 | auto e3 = ecs.AddEntity(); 530 | 531 | ASSERT_EQ(e1.GetId(), 0); 532 | ASSERT_EQ(e2.GetId(), 1); 533 | ASSERT_EQ(e3.GetId(), 2); 534 | 535 | // Create a hole in the list of entities 536 | ecs.RemoveEntity(e2); 537 | ASSERT_EQ(ecs.Size(), 2); 538 | EXPECT_THROW(ecs.RemoveEntity(e2), std::logic_error); 539 | ASSERT_EQ(ecs.Size(), 2); 540 | 541 | // The hole should be filled 542 | auto e4 = ecs.AddEntity(); 543 | ASSERT_EQ(e4.GetId(), 1); 544 | } 545 | 546 | TEST(ECS, FillBigHoleTest) { 547 | ecs::ECSManager ecs; 548 | auto e1 = ecs.AddEntity(); 549 | auto e2 = ecs.AddEntity(); 550 | auto e3 = ecs.AddEntity(); 551 | auto e4 = ecs.AddEntity(); 552 | auto e5 = ecs.AddEntity(); 553 | 554 | ASSERT_EQ(e1.GetId(), 0); 555 | ASSERT_EQ(e2.GetId(), 1); 556 | ASSERT_EQ(e3.GetId(), 2); 557 | ASSERT_EQ(e4.GetId(), 3); 558 | ASSERT_EQ(e5.GetId(), 4); 559 | ASSERT_EQ(ecs.Size(), 5); 560 | 561 | // Create a hole in the list of entities 562 | ecs.RemoveEntity(e2); 563 | ecs.RemoveEntity(e3); 564 | ecs.RemoveEntity(e4); 565 | 566 | ASSERT_EQ(ecs.Size(), 2); 567 | 568 | // The hole should be filled 569 | auto e6 = ecs.AddEntity(); 570 | ASSERT_EQ(e6.GetId(), 1); 571 | auto e7 = ecs.AddEntity(); 572 | ASSERT_EQ(e7.GetId(), 2); 573 | auto e8 = ecs.AddEntity(); 574 | ASSERT_EQ(e8.GetId(), 3); 575 | 576 | // Remove and fill first; 577 | ecs.RemoveEntity(e1); 578 | auto e9 = ecs.AddEntity(); 579 | ASSERT_EQ(e9.GetId(), 0); 580 | 581 | // Remove and fill last; 582 | ecs.RemoveEntity(e5); 583 | auto e10 = ecs.AddEntity(); 584 | ASSERT_EQ(e10.GetId(), 4); 585 | 586 | ASSERT_EQ(ecs.Size(), 5); 587 | } 588 | 589 | TEST(ECS, ReuseSlots) { 590 | ecs::ECSManager ecs; 591 | 592 | // Will go out of bounds and throw if slots aren't reused. 593 | for (int i = 0; i < 100000; i++) { 594 | auto entity = ecs.AddEntity(); 595 | ecs.RemoveEntity(entity); 596 | } 597 | } 598 | 599 | TEST(ECS, ConcurencySystem) { 600 | ecs::ECSManager ecs; 601 | for (int i = 0; i < 1024 - 1; i++) { 602 | auto val = ecs.AddEntity(); 603 | } 604 | 605 | for (auto [i, f]: ecs.GetSystem()) { 606 | i = 42; 607 | f = 3.14; 608 | } 609 | 610 | std::vector> results; 611 | 612 | auto task = [](auto tuple) { 613 | auto [i, f] = tuple; 614 | i += 12; 615 | f = 2; 616 | }; 617 | for (auto tuple: ecs.GetSystem()) { 618 | results.push_back(std::async(std::launch::async, task, tuple)); 619 | } 620 | 621 | for (const auto &future: results) { 622 | future.wait(); 623 | } 624 | for (auto [i, f]: ecs.GetSystem()) { 625 | ASSERT_EQ(i, 54); 626 | ASSERT_EQ(f, 2); 627 | } 628 | } 629 | 630 | TEST(ECS, LoopComparision) { 631 | int count1 = 0; 632 | int count2 = 0; 633 | int nrRuns = 10; 634 | for (int i = 0; i < nrRuns; i++) { 635 | 636 | using namespace std::chrono; 637 | struct obj { 638 | int i = 5; 639 | std::string bla = "hello"; 640 | std::optional oi; 641 | std::vector ints = {1, 2, 3, 4, 5, 6}; 642 | }; 643 | struct ComplexObject { 644 | int i = 0; 645 | std::string str1 = "hello"; 646 | float f1 = 4251414.0f; 647 | std::string str2 = "world"; 648 | float f2 = 4251414.0f; 649 | obj o1; 650 | obj o2; 651 | }; 652 | 653 | ecs::ECSManager ecs; 654 | std::array a1; 655 | 656 | for (int j = 0; j < 1024 - 1; j++) { 657 | auto val = ecs.AddEntity(); 658 | ecs.Add(val, {}); 659 | a1[j] = {}; 660 | } 661 | 662 | auto start1 = high_resolution_clock::now(); 663 | for (auto &o: a1) { 664 | o.i = 5; 665 | } 666 | auto end1 = high_resolution_clock::now(); 667 | auto duration1 = duration_cast(end1 - start1); 668 | 669 | auto start2 = high_resolution_clock::now(); 670 | for (auto [o]: ecs.GetSystem()) { 671 | o.i = 5; 672 | } 673 | auto end2 = high_resolution_clock::now(); 674 | auto duration2 = duration_cast(end2 - start2); 675 | 676 | // ECS is about ~2-3 slower than a std::array on non-trivial objects. 677 | // On trivial objects it's about 100 times slower. 678 | count1 += duration1.count(); 679 | count2 += duration2.count(); 680 | } 681 | //ASSERT_EQ(count1/nrRuns, count2/nrRuns); 682 | } 683 | 684 | TEST(ECS, ConstrictedTypes) { 685 | struct [[maybe_unused]] fooType { 686 | fooType() = delete; 687 | int bla = 5; 688 | }; 689 | /** 690 | * Produces compile errors from class requirements 691 | ecs::ECSManager<> empty; 692 | ecs::ECSManager defaultInitializable; 693 | ecs::ECSManager ref; 694 | ecs::ECSManager ptr; 695 | ecs::ECSManager const; 696 | ecs::ECSManager volatile; 697 | */ 698 | } 699 | 700 | TEST(ECS, HoleTest) { 701 | ecs::ECSManager ecs; 702 | auto e1 = ecs.AddEntity(); 703 | auto e2 = ecs.AddEntity(); 704 | auto e3 = ecs.AddEntity(); 705 | auto e4 = ecs.AddEntity(); 706 | auto e5 = ecs.AddEntity(); 707 | 708 | ASSERT_EQ(e1.GetId(), 0); 709 | ASSERT_EQ(e2.GetId(), 1); 710 | ASSERT_EQ(e3.GetId(), 2); 711 | ASSERT_EQ(e4.GetId(), 3); 712 | ASSERT_EQ(e5.GetId(), 4); 713 | ASSERT_EQ(ecs.Size(), 5); 714 | 715 | // Create a hole in the list of entities 716 | ecs.RemoveEntity(e2); 717 | ecs.RemoveEntity(e3); 718 | ecs.RemoveEntity(e4); 719 | 720 | ASSERT_EQ(ecs.Size(), 2); 721 | 722 | // The hole should be filled 723 | auto e6 = ecs.AddEntity(); 724 | ASSERT_EQ(e6.GetId(), 1); 725 | auto e7 = ecs.AddEntity(); 726 | ASSERT_EQ(e7.GetId(), 2); 727 | auto e8 = ecs.AddEntity(); 728 | ASSERT_EQ(e8.GetId(), 3); 729 | 730 | // Remove and fill first; 731 | ecs.RemoveEntity(e1); 732 | auto e9 = ecs.AddEntity(); 733 | ASSERT_EQ(e9.GetId(), 0); 734 | 735 | // Remove and fill last; 736 | ecs.RemoveEntity(e5); 737 | auto e10 = ecs.AddEntity(); 738 | ASSERT_EQ(e10.GetId(), 4); 739 | 740 | ASSERT_EQ(ecs.Size(), 5); 741 | } 742 | 743 | TEST(ECS, SystemPart) { 744 | ecs::ECSManager ecs; 745 | for (int i = 0; i < 1024 - 1; i++) { 746 | ecs.BuildEntity(0, 1.2f); 747 | } 748 | 749 | int maxParts = 3; 750 | int n = 0; 751 | for (int i = 0; i < maxParts; i++) { 752 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 753 | i = 42; 754 | f = 3.14; 755 | n++; 756 | } 757 | } 758 | ASSERT_EQ(n, 1023); 759 | for (auto [i, f]: ecs.GetSystem()) { 760 | ASSERT_EQ(i, 42); 761 | ASSERT_FLOAT_EQ(f, 3.14f); 762 | } 763 | } 764 | 765 | 766 | TEST(ECS, SystemPartConcurent) { 767 | ecs::ECSManager ecs; 768 | for (int i = 0; i < 10000; i++) { 769 | ecs.BuildEntity(0, 1.2f); 770 | } 771 | 772 | std::vector> results; 773 | int maxParts = 10; 774 | for (int i = 0; i < maxParts; i++) { 775 | results.push_back(std::async(std::launch::async, [&ecs, i, maxParts](){ 776 | for (auto [ii, f]: ecs.GetSystemPart(i, maxParts)) { 777 | ii = 42; 778 | f = 3.14; 779 | } 780 | })); 781 | } 782 | 783 | for (const auto &future: results) { 784 | future.wait(); 785 | } 786 | for (auto [i, f]: ecs.GetSystem()) { 787 | ASSERT_EQ(i, 42); 788 | ASSERT_FLOAT_EQ(f, 3.14f); 789 | } 790 | } 791 | 792 | TEST(ECS, SystemPartValidateDifferentSplits_8) 793 | { 794 | ecs::ECSManager ecs; 795 | for (int i = 0; i < 15; i++) { 796 | ecs.BuildEntity(0, 1.2f); 797 | } 798 | int maxParts = 17; 799 | int n = 0; 800 | for (int i = 0; i < maxParts; i++) { 801 | int nn = 0; 802 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 803 | i = 42; 804 | f = 3.14; 805 | n++; 806 | nn++; 807 | } 808 | if (i == 16) { 809 | ASSERT_EQ(nn, 15); 810 | } else { 811 | ASSERT_EQ(nn, 0); 812 | } 813 | } 814 | ASSERT_EQ(n, 15); 815 | for (auto [i, f]: ecs.GetSystem()) { 816 | ASSERT_EQ(i, 42); 817 | ASSERT_FLOAT_EQ(f, 3.14f); 818 | } 819 | } 820 | 821 | TEST(ECS, SystemPartValidateDifferentSplits_7) 822 | { 823 | ecs::ECSManager ecs; 824 | for (int i = 0; i < 15; i++) { 825 | ecs.BuildEntity(0, 1.2f); 826 | } 827 | int maxParts = 12; 828 | int n = 0; 829 | for (int i = 0; i < maxParts; i++) { 830 | int nn = 0; 831 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 832 | i = 42; 833 | f = 3.14; 834 | n++; 835 | nn++; 836 | } 837 | if (i == 11) { 838 | ASSERT_EQ(nn, 4); 839 | } else { 840 | ASSERT_EQ(nn, 1); 841 | } 842 | } 843 | ASSERT_EQ(n, 15); 844 | for (auto [i, f]: ecs.GetSystem()) { 845 | ASSERT_EQ(i, 42); 846 | ASSERT_FLOAT_EQ(f, 3.14f); 847 | } 848 | } 849 | 850 | TEST(ECS, SystemPartValidateDifferentSplits_6) 851 | { 852 | ecs::ECSManager ecs; 853 | for (int i = 0; i < 15; i++) { 854 | ecs.BuildEntity(0, 1.2f); 855 | } 856 | int maxParts = 15; 857 | int n = 0; 858 | for (int i = 0; i < maxParts; i++) { 859 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 860 | i = 42; 861 | f = 3.14; 862 | n++; 863 | } 864 | } 865 | ASSERT_EQ(n, 15); 866 | for (auto [i, f]: ecs.GetSystem()) { 867 | ASSERT_EQ(i, 42); 868 | ASSERT_FLOAT_EQ(f, 3.14f); 869 | } 870 | } 871 | 872 | TEST(ECS, SystemPartValidateDifferentSplits_5) 873 | { 874 | ecs::ECSManager ecs; 875 | for (int i = 0; i < 15; i++) { 876 | ecs.BuildEntity(0, 1.2f); 877 | } 878 | int maxParts = 3; 879 | int n = 0; 880 | for (int i = 0; i < maxParts; i++) { 881 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 882 | i = 42; 883 | f = 3.14; 884 | n++; 885 | } 886 | } 887 | ASSERT_EQ(n, 15); 888 | for (auto [i, f]: ecs.GetSystem()) { 889 | ASSERT_EQ(i, 42); 890 | ASSERT_FLOAT_EQ(f, 3.14f); 891 | } 892 | } 893 | TEST(ECS, SystemPartValidateDifferentSplits_4) 894 | { 895 | ecs::ECSManager ecs; 896 | for (int i = 0; i < 15; i++) { 897 | ecs.BuildEntity(0, 1.2f); 898 | } 899 | int maxParts = 2; 900 | int n = 0; 901 | for (int i = 0; i < maxParts; i++) { 902 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 903 | i = 42; 904 | f = 3.14; 905 | n++; 906 | } 907 | } 908 | ASSERT_EQ(n, 15); 909 | for (auto [i, f]: ecs.GetSystem()) { 910 | ASSERT_EQ(i, 42); 911 | ASSERT_FLOAT_EQ(f, 3.14f); 912 | } 913 | } 914 | 915 | TEST(ECS, SystemPartValidateDifferentSplits_3) 916 | { 917 | ecs::ECSManager ecs; 918 | for (int i = 0; i < 10; i++) { 919 | ecs.BuildEntity(0, 1.2f); 920 | } 921 | int maxParts = 3; 922 | int n = 0; 923 | for (int i = 0; i < maxParts; i++) { 924 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 925 | i = 42; 926 | f = 3.14; 927 | n++; 928 | } 929 | } 930 | ASSERT_EQ(n, 10); 931 | for (auto [i, f]: ecs.GetSystem()) { 932 | ASSERT_EQ(i, 42); 933 | ASSERT_FLOAT_EQ(f, 3.14f); 934 | } 935 | } 936 | TEST(ECS, SystemPartValidateDifferentSplits_2) 937 | { 938 | ecs::ECSManager ecs; 939 | for (int i = 0; i < 10; i++) { 940 | ecs.BuildEntity(0, 1.2f); 941 | } 942 | int maxParts = 2; 943 | int n = 0; 944 | for (int i = 0; i < maxParts; i++) { 945 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 946 | i = 42; 947 | f = 3.14; 948 | n++; 949 | } 950 | } 951 | ASSERT_EQ(n, 10); 952 | for (auto [i, f]: ecs.GetSystem()) { 953 | ASSERT_EQ(i, 42); 954 | ASSERT_FLOAT_EQ(f, 3.14f); 955 | } 956 | } 957 | TEST(ECS, SystemPartValidateDifferentSplits_1) 958 | { 959 | ecs::ECSManager ecs; 960 | for (int i = 0; i < 10; i++) { 961 | ecs.BuildEntity(0, 1.2f); 962 | } 963 | int maxParts = 1; 964 | int n = 0; 965 | for (int i = 0; i < maxParts; i++) { 966 | for (auto [i, f]: ecs.GetSystemPart(i, maxParts)) { 967 | i = 42; 968 | f = 3.14; 969 | n++; 970 | } 971 | } 972 | ASSERT_EQ(n, 10); 973 | for (auto [i, f]: ecs.GetSystem()) { 974 | ASSERT_EQ(i, 42); 975 | ASSERT_FLOAT_EQ(f, 3.14f); 976 | } 977 | } 978 | 979 | TEST(ECS, SystemPartValidateID) 980 | { 981 | ecs::ECSManager ecs; 982 | for (int i = 0; i < 10; i++) { 983 | ecs.BuildEntity(0, 1.2f); 984 | } 985 | int maxParts = 1; 986 | int n = 0; 987 | std::vector ids; 988 | for (int i = 0; i < maxParts; i++) { 989 | for (auto [i, f, id]: ecs.GetSystemPart(i, maxParts)) { 990 | i = 42; 991 | f = 3.14; 992 | n++; 993 | ids.push_back(id); 994 | } 995 | } 996 | ASSERT_EQ(n, 10); 997 | for (auto [i, f]: ecs.GetSystem()) { 998 | ASSERT_EQ(i, 42); 999 | ASSERT_FLOAT_EQ(f, 3.14f); 1000 | } 1001 | size_t idNr = 0; 1002 | for (auto id: ids) { 1003 | ASSERT_TRUE(ecs.HasEntity(id)); 1004 | ASSERT_EQ(id.GetId(), idNr); 1005 | idNr++; 1006 | } 1007 | } 1008 | 1009 | TEST(ECS, HasTypes) 1010 | { 1011 | using TEcs = ecs::ECSManager; 1012 | static_assert(ecs::HasTypes()); 1013 | static_assert(ecs::HasTypes()); 1014 | static_assert(ecs::HasTypes()); 1015 | static_assert(not ecs::HasTypes()); 1016 | static_assert(not ecs::HasTypes()); 1017 | } 1018 | 1019 | struct TestAttribute { 1020 | bool bla = true; 1021 | int speed = 1; 1022 | }; 1023 | 1024 | struct TestEffect { 1025 | int speedMultiplier = 2; 1026 | using Attribute = TestAttribute; 1027 | 1028 | void Apply(Attribute& attribute) const { 1029 | attribute.speed *= speedMultiplier; 1030 | } 1031 | }; 1032 | 1033 | struct TestEffect2 { 1034 | int speedDivider = 2; 1035 | using Attribute = TestAttribute; 1036 | 1037 | void Apply(Attribute& attribute) const { 1038 | attribute.speed /= speedDivider; 1039 | } 1040 | }; 1041 | 1042 | struct TestEffect3 { 1043 | int speedDivider = 2; 1044 | // Won't work due to missing Attribute. 1045 | //using Attribute = TestAttribute; 1046 | 1047 | void Apply(auto& attribute) const { 1048 | attribute.speed /= speedDivider; 1049 | } 1050 | }; 1051 | 1052 | struct TestEffect4 { 1053 | int speedDivider = 2; 1054 | // Won't work due to missing Attribute. 1055 | using Attribute = TestAttribute; 1056 | 1057 | void ApplyWrongSignature(Attribute& attribute) const { 1058 | attribute.speed /= speedDivider; 1059 | } 1060 | }; 1061 | 1062 | TEST(ECSAttributesEffectsManager, construct) 1063 | { 1064 | ecs::AttributesEffectsManager, TestEffect, TestEffect2> attributes; 1065 | } 1066 | 1067 | TEST(ECSAttributesEffectsManager, concepts) 1068 | { 1069 | ecs::AttributesEffectsManager, TestEffect, TestEffect2>(); 1070 | //ecs::AttributesEffectsManager, TestEffect, TestEffect2>(); //Not correct attribute 1071 | //ecs::AttributesEffectsManager, TestEffect4>(); //Incorrect effect Attribute variable 1072 | //ecs::AttributesEffectsManager, TestEffect3>(); //Incorrect effect wrong signature 1073 | } 1074 | 1075 | TEST(ECSAttributesEffectsManager, effect) 1076 | { 1077 | using TEcs = ecs::ECSManager; 1078 | TEcs ecs; 1079 | auto entity = ecs.BuildEntity(int{1}); 1080 | 1081 | ecs::AttributesEffectsManager, TestEffect, TestEffect2> attributes; 1082 | { 1083 | const auto& attribute = attributes.Get(entity); 1084 | ASSERT_EQ(1, attribute.speed); 1085 | } 1086 | 1087 | attributes.Add(entity, TestEffect{}); 1088 | { 1089 | const auto& attribute = attributes.Get(entity); 1090 | ASSERT_EQ(2, attribute.speed); 1091 | } 1092 | attributes.Add(entity, TestEffect{}); 1093 | { 1094 | const auto& attribute = attributes.Get(entity); 1095 | ASSERT_EQ(4, attribute.speed); 1096 | } 1097 | attributes.Add(entity, TestEffect{4}); 1098 | { 1099 | const auto& attribute = attributes.Get(entity); 1100 | ASSERT_EQ(16, attribute.speed); 1101 | } 1102 | attributes.Add(entity, TestEffect2{}); 1103 | { 1104 | const auto& attribute = attributes.Get(entity); 1105 | ASSERT_EQ(8, attribute.speed); 1106 | } 1107 | } 1108 | 1109 | TEST(ECSAttributesEffectsManager, modify) 1110 | { 1111 | using TEcs = ecs::ECSManager; 1112 | TEcs ecs; 1113 | auto entity = ecs.BuildEntity(int{1}); 1114 | 1115 | ecs::AttributesEffectsManager, TestEffect, TestEffect2> attributes; 1116 | attributes.Modify(entity, TestAttribute{false, 4}); 1117 | { 1118 | const auto &attribute = attributes.Get(entity); 1119 | ASSERT_EQ(false, attribute.bla); 1120 | ASSERT_EQ(4, attribute.speed); 1121 | } 1122 | attributes.Add(entity, TestEffect{}); 1123 | { 1124 | const auto& attribute = attributes.Get(entity); 1125 | ASSERT_EQ(8, attribute.speed); 1126 | ASSERT_EQ(false, attribute.bla); 1127 | } 1128 | attributes.Reset(entity); 1129 | { 1130 | const auto& attribute = attributes.Get(entity); 1131 | ASSERT_EQ(1, attribute.speed); 1132 | ASSERT_EQ(true, attribute.bla); 1133 | } 1134 | } 1135 | 1136 | TEST(ECSAttributesEffectsManager, GetEffect) 1137 | { 1138 | using TEcs = ecs::ECSManager; 1139 | TEcs ecs; 1140 | auto entity = ecs.BuildEntity(int{1}); 1141 | 1142 | ecs::AttributesEffectsManager, TestEffect, TestEffect2> attributes; 1143 | { 1144 | const auto& attribute = attributes.Get(entity); 1145 | ASSERT_EQ(1, attribute.speed); 1146 | auto effects = attributes.GetEffects(entity); 1147 | ASSERT_EQ(effects.size(), 0); 1148 | auto effects2 = attributes.GetEffects(entity); 1149 | ASSERT_EQ(effects2.size(), 0); 1150 | } 1151 | 1152 | attributes.Add(entity, TestEffect{}); 1153 | attributes.Add(entity, TestEffect{}); 1154 | attributes.Add(entity, TestEffect{4}); 1155 | attributes.Add(entity, TestEffect2{}); 1156 | 1157 | std::vector ids; 1158 | { 1159 | auto effects = attributes.GetEffects(entity); 1160 | ASSERT_EQ(effects.size(), 3); 1161 | for (const auto& effect : effects) { 1162 | ids.push_back(effect.Id); 1163 | } 1164 | } 1165 | { 1166 | auto effects = attributes.GetEffects(entity); 1167 | ASSERT_EQ(effects.size(), 1); 1168 | for (const auto& effect : effects) { 1169 | ids.push_back(effect.Id); 1170 | } 1171 | } 1172 | for (const auto& id : ids) { 1173 | ASSERT_EQ(std::count(ids.begin(), ids.end(), id), 1); 1174 | } 1175 | } 1176 | 1177 | TEST(ECSAttributesEffectsManager, RemoveEffect) 1178 | { 1179 | using TEcs = ecs::ECSManager; 1180 | TEcs ecs; 1181 | auto entity = ecs.BuildEntity(int{1}); 1182 | 1183 | ecs::AttributesEffectsManager, TestEffect, TestEffect2> attributes; 1184 | 1185 | attributes.Add(entity, TestEffect{}); 1186 | attributes.Add(entity, TestEffect{}); 1187 | attributes.Add(entity, TestEffect{4}); 1188 | attributes.Add(entity, TestEffect2{}); 1189 | 1190 | { 1191 | { 1192 | const auto& attribute = attributes.Get(entity); 1193 | ASSERT_EQ(8, attribute.speed); 1194 | } 1195 | auto effects = attributes.GetEffects(entity); 1196 | attributes.Remove(entity, {effects[0], effects[2]}); 1197 | { 1198 | const auto& attribute = attributes.Get(entity); 1199 | ASSERT_EQ(1, attribute.speed); 1200 | } 1201 | } 1202 | { 1203 | auto effects = attributes.GetEffects(entity); 1204 | ASSERT_EQ(effects.size(), 1); 1205 | attributes.Remove(entity, effects); 1206 | { 1207 | const auto& attribute = attributes.Get(entity); 1208 | ASSERT_EQ(2, attribute.speed); 1209 | } 1210 | } 1211 | } 1212 | 1213 | 1214 | int main(int argc, char **argv) { 1215 | ::testing::InitGoogleTest(&argc, argv); 1216 | return RUN_ALL_TESTS(); 1217 | } 1218 | --------------------------------------------------------------------------------