├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build.cmd ├── documentation.html ├── include ├── VECS.h ├── VECSArchetype.h ├── VECSHandle.h ├── VECSMutex.h ├── VECSRegistry.h ├── VECSSlotMap.h ├── VECSVector.h └── old │ ├── VECS.h │ ├── VECSArchetype.h │ ├── VECSHashMap.h │ ├── VECSIterator.h │ └── VECS_old2.h └── tests ├── CMakeLists.txt ├── testvecs.cpp └── testvecs_vecs.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | build/ 3 | docs/ 4 | extern/imgui 5 | extern/glm 6 | extern/glTF 7 | 8 | **/CMakeFiles 9 | **/*.vcxproj 10 | **/*.vcxproj.user 11 | **/*.vcxproj.filters 12 | [Dd]ebug/ 13 | [Rr]elease/ 14 | **/*.ini 15 | **/*.tcl 16 | **/*.exe 17 | **/*.pdb 18 | **/*.ilk 19 | **/*.obj 20 | vc140.pdb 21 | 22 | #x64/ 23 | #bin/ 24 | #html/ 25 | 26 | ## Ignore Visual Studio temporary files, build results, and 27 | ## files generated by popular Visual Studio add-ons. 28 | 29 | # cmake files 30 | #CMakeLists.txt.user 31 | #CMakeCache.txt 32 | #**/CMakeFiles 33 | #**/CMakeFiles/* 34 | #CMakeScripts 35 | #Testing 36 | #Makefile 37 | #cmake_install.cmake 38 | #install_manifest.txt 39 | #compile_commands.json 40 | #CTestTestfile.cmake 41 | #CMakeDoxyfile.in 42 | #CMakeDoxygenDefaults.cmake 43 | #_deps 44 | #**/examples.dir 45 | #**/*.dir 46 | #**/*.cmake 47 | #**/*.depend 48 | 49 | # MSVC cmake project files 50 | #ALL_BUILD.vcxproj 51 | #ZERO_CHECK.vcxproj 52 | #doxygen.vcxproj 53 | #**/src/vve.vcxproj 54 | #ALL_BUILD.vcxproj.filters 55 | #ZERO_CHECK.vcxproj.filters 56 | #**/src/vve.vcxproj.filters 57 | #doxygen.vcxproj.filters 58 | #Doxyfile.doxygen 59 | #**/*.vcxproj 60 | #**/*.vcxproj.user 61 | #**/*.vcxproj.filters 62 | #**/*.vcxproj.filters.user 63 | #ALL_BUILD.vcxproj.user 64 | #**/*.sln 65 | 66 | #[Dd]ebug/ 67 | #[Rr]elease/ 68 | #**/*.ini 69 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.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 | "configurations": [ 7 | { 8 | "name": "MSVC Launch Debug", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/tests/Debug/testvecs.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false 17 | }, 18 | { 19 | "name": "MSVC Launch Release", 20 | "type": "cppvsdbg", 21 | "request": "launch", 22 | "program": "${workspaceFolder}/build/tests/Release/testvecs.exe", 23 | "args": [], 24 | "stopAtEntry": false, 25 | "cwd": "${workspaceFolder}", 26 | "environment": [], 27 | "externalConsole": false 28 | }, 29 | { 30 | "name": "Clang Launch", 31 | "type": "cppvsdbg", 32 | "request": "launch", 33 | "program": "${workspaceFolder}/build/tests/testvecs.exe", 34 | "args": [], 35 | "stopAtEntry": false, 36 | "cwd": "${workspaceFolder}", 37 | "environment": [], 38 | "externalConsole": false 39 | }, 40 | { 41 | "name": "Ubuntu Clang Launch", 42 | "type": "cppdbg", 43 | "request": "launch", 44 | "program": "${workspaceFolder}/build/tests/testvecs", 45 | "args": [], 46 | "stopAtEntry": false, 47 | "cwd": "${workspaceFolder}", 48 | "environment": [], 49 | "externalConsole": false 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "algorithm": "cpp", 4 | "any": "cpp", 5 | "array": "cpp", 6 | "atomic": "cpp", 7 | "bit": "cpp", 8 | "cctype": "cpp", 9 | "charconv": "cpp", 10 | "chrono": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "compare": "cpp", 14 | "concepts": "cpp", 15 | "condition_variable": "cpp", 16 | "coroutine": "cpp", 17 | "cstdarg": "cpp", 18 | "cstddef": "cpp", 19 | "cstdint": "cpp", 20 | "cstdio": "cpp", 21 | "cstdlib": "cpp", 22 | "cstring": "cpp", 23 | "ctime": "cpp", 24 | "cwchar": "cpp", 25 | "deque": "cpp", 26 | "exception": "cpp", 27 | "resumable": "cpp", 28 | "format": "cpp", 29 | "forward_list": "cpp", 30 | "fstream": "cpp", 31 | "functional": "cpp", 32 | "future": "cpp", 33 | "initializer_list": "cpp", 34 | "iomanip": "cpp", 35 | "ios": "cpp", 36 | "iosfwd": "cpp", 37 | "iostream": "cpp", 38 | "istream": "cpp", 39 | "iterator": "cpp", 40 | "limits": "cpp", 41 | "list": "cpp", 42 | "locale": "cpp", 43 | "map": "cpp", 44 | "memory": "cpp", 45 | "memory_resource": "cpp", 46 | "mutex": "cpp", 47 | "new": "cpp", 48 | "numeric": "cpp", 49 | "optional": "cpp", 50 | "ostream": "cpp", 51 | "queue": "cpp", 52 | "ranges": "cpp", 53 | "ratio": "cpp", 54 | "set": "cpp", 55 | "shared_mutex": "cpp", 56 | "span": "cpp", 57 | "sstream": "cpp", 58 | "stdexcept": "cpp", 59 | "stop_token": "cpp", 60 | "streambuf": "cpp", 61 | "string": "cpp", 62 | "system_error": "cpp", 63 | "thread": "cpp", 64 | "tuple": "cpp", 65 | "type_traits": "cpp", 66 | "typeindex": "cpp", 67 | "typeinfo": "cpp", 68 | "unordered_map": "cpp", 69 | "utility": "cpp", 70 | "variant": "cpp", 71 | "vector": "cpp", 72 | "xfacet": "cpp", 73 | "xhash": "cpp", 74 | "xiosbase": "cpp", 75 | "xlocale": "cpp", 76 | "xlocbuf": "cpp", 77 | "xlocinfo": "cpp", 78 | "xlocmes": "cpp", 79 | "xlocmon": "cpp", 80 | "xlocnum": "cpp", 81 | "xloctime": "cpp", 82 | "xmemory": "cpp", 83 | "xstring": "cpp", 84 | "xtr1common": "cpp", 85 | "xtree": "cpp", 86 | "xutility": "cpp", 87 | "bitset": "cpp", 88 | "latch": "cpp", 89 | "stack": "cpp", 90 | "unordered_set": "cpp", 91 | "random": "cpp", 92 | "print": "cpp" 93 | } 94 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "cppbuild", 5 | "label": "C/C++: clang++.exe build active file", 6 | "command": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang++.exe", 7 | "args": [ 8 | "-fcolor-diagnostics", 9 | "-fansi-escape-codes", 10 | "-g", 11 | "${file}", 12 | "-o", 13 | "${fileDirname}\\${fileBasenameNoExtension}.exe" 14 | ], 15 | "options": { 16 | "cwd": "${fileDirname}" 17 | }, 18 | "problemMatcher": [ 19 | "$gcc" 20 | ], 21 | "group": "build", 22 | "detail": "Task generated by Debugger." 23 | }, 24 | { 25 | "type": "cppbuild", 26 | "label": "C/C++: cl.exe build active file", 27 | "command": "cl.exe", 28 | "args": [ 29 | "/Zi", 30 | "/EHsc", 31 | "/nologo", 32 | "/Fe${fileDirname}\\${fileBasenameNoExtension}.exe", 33 | "${file}" 34 | ], 35 | "options": { 36 | "cwd": "${fileDirname}" 37 | }, 38 | "problemMatcher": [ 39 | "$msCompile" 40 | ], 41 | "group": { 42 | "kind": "build", 43 | "isDefault": true 44 | }, 45 | "detail": "Task generated by Debugger." 46 | } 47 | ], 48 | "version": "2.0.0" 49 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.5.0) 3 | project ( 4 | "ViennaEntityComponentSystem" 5 | VERSION 2.0.0 6 | DESCRIPTION "An entity component system for game engines" 7 | LANGUAGES CXX 8 | ) 9 | 10 | # enforce C++23 standard 11 | set(CMAKE_CXX_STANDARD 20) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 15 | set(CMAKE_CXX_FLAGS_RELEASE /O2) 16 | add_compile_options(/await) 17 | add_compile_options(/EHsc) 18 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 19 | add_compile_options(-stdlib=libc++) 20 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 21 | add_compile_options(-stdlib=libc++) 22 | if(UNIX) 23 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -v") 24 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi") 25 | endif() 26 | elseif(CMAKE_COMPILER_IS_GNUCXX) 27 | endif() 28 | 29 | include_directories (${PROJECT_SOURCE_DIR}/include) 30 | 31 | include(FetchContent) # once in the project to include the module 32 | 33 | FetchContent_Declare(viennastrongtype 34 | GIT_REPOSITORY https://github.com/hlavacs/ViennaStrongType.git 35 | GIT_TAG main) 36 | 37 | FetchContent_MakeAvailable(viennastrongtype) 38 | 39 | include_directories (${viennastrongtype_SOURCE_DIR}/include) 40 | 41 | FetchContent_Declare(viennatypelistlibrary 42 | GIT_REPOSITORY https://github.com/hlavacs/ViennaTypeListLibrary.git 43 | GIT_TAG main) 44 | 45 | FetchContent_MakeAvailable(viennatypelistlibrary) 46 | 47 | include_directories (${viennatypelistlibrary_SOURCE_DIR}/include) 48 | 49 | 50 | # Testing 51 | 52 | include(CTest) 53 | 54 | add_subdirectory(tests) 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 hlavacs 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 | # Vienna Entity Component System (VECS) 2 | 3 | The Vienna Entity Component System (VECS) is a C++20 based ECS for game engines. An ECS is a data structure storing *entities* of different types. 4 | Entity types consist of a number of *components*. 5 | Different entity types can be composed of different components. 6 | 7 | An ECS is a generic storage for such entities and their components. Entities can be composed and created, retrieved, updated, or erased from the storage. In a game engine, typically a so-called *system* can have access to one or several components it is interested in, and work on them. For example, a *physics* system may be interested into components position, velocity, orientation, mass and moment of inertia. Another system *animation* is only interested into position, orientation, and animation data. 8 | Systems do not have to be specially defined, anything that specifies a number of components it is interested in and then loops over all entities that contain these components can be seen as a system. 9 | 10 | Important features of VECS are: 11 | * C++20 12 | * Header only, simply include the headers to your project 13 | * Easy to use 14 | * Supports multithreading and parallel accesses. 15 | 16 | VECS is a container for entities. Entities are composed of an arbitrary number of components of simple plain old data types (PODs). PODs can be copied, moved, but cannot be pointers or references. They can also be copyable or movable classes like *std::string*. 17 | The standard operation in each game loop is to loop over a *subset* of such components concurrently. 18 | Data layout is organized in such a way to make optimal use of caches for exactly this main use case. 19 | All entities containing the same component types are grouped into *archetypes*, i.e., separate data structures 20 | storing these components, each component in continuous memory. Here, empty slots are never allowed, 21 | i.e., if an entity and all its components is erased, the components at the end of the data structure are moved into the now 22 | empty slots to fill up the space. 23 | Thus when iterating over *N* components, accessing each component can make optimal use of cache prefetching, i.e., 24 | hiding slow data transfers from main memory by loading the data up front before being actually accessed. 25 | 26 | VECS internally uses the following data structures: 27 | * *Vector*: a container like a *std::vector*, but using segments to store data. Inside a segment, data is stored contiguously. Pointers to data are invalidated only if data is moved or erased. 28 | * *SlotMap*: a map that maps an integer index to an archetype and an index inside the archetype. *SlotMap* is based on *Vector* and **never shrinks**. Each entry also contains a *version* number, which is increased 29 | each time an entity is erased from VECS. 30 | * *Handle*: Handles identify entities. For this, they contain an integer *index* into the SlotMap, and a *version* number. Handles point to existing entities only if their version numbers match. A handle points to an erased entity if its version number does not match the SlotMap version number. 31 | * *ComponentMap*: is based on *Vector* and stores one specific data type. 32 | * *Archetype*: Contains all component maps of entities having the same set of component types. 33 | * *View*: allows to select a subset of component types and entities and can create Iterators for looping. 34 | * *Iterator*: can be used to loop over a subset of entities and component types. 35 | * *Ref\* is like a C++ reference to a data component, but is based on the SlotMap entry and autmatically finds components, even if their entities have been moved to other archetypes. 36 | * *Registry*: the main class representing a container for entities. Programs can hold an arbitrary number of 37 | Registry instances any time, they do not interfere with each other. 38 | * *LockGuard* and *LockGuardShared*: used to protect data structures when compiled for parallel mode. Are empty when compiled for sequential mode. 39 | 40 | 41 | ## The VECS Include Files 42 | 43 | VECS is a header only library, consisting only of header files. Include *VECS.h* into your source code to use VECS. VECS depends on other header only projects: 44 | * Vienna Type List Library (VTLL), see https://github.com/hlavacs/ViennaTypeListLibrary. 45 | * Vienna Strong Type (VSTY), see https://github.com/hlavacs/ViennaStrongType. 46 | 47 | Both are automatically downloaded through *CMake* configuration: 48 | 49 | ``` 50 | cmake -S . -Bbuild -A x64 51 | cd build 52 | cmake --build . --config Release 53 | cmake --build . --config Debug 54 | cd .. 55 | ``` 56 | 57 | 58 | ## VECS Usage 59 | 60 | You can create an ECS with such a statement: 61 | 62 | ```C 63 | #define REGISTRYTYPE_SEQUENTIAL //is the defualt, so you dont need it 64 | #include "VECS.h" 65 | 66 | int main() { 67 | vecs::Registry system; 68 | } 69 | ``` 70 | Now *system* is a container of entities and their components. 71 | VECS can be compiled in two versions by defining a macro before including it. In REGISTRYTYPE_SEQUENTIAL mode (default if none is defined) all operations are supposed to be done sequentially, there is no internal synchronization taking place. In REGISTRYTYPE_PARALLEL mode VECS uses mutexes to protect internal data structures from corruption, when access are done from multiple threads concurrently (currently NOT implemented). 72 | 73 | Entities can be created by calling *Insert()*: 74 | 75 | ```C 76 | vecs::Handle h1 = system.Insert(5, 3.0f, 4.0); 77 | ``` 78 | 79 | Component values and their types are automatically determined by the function parameter list. In the example an entity is created that holds an integer, a float and a double as components, with the respective values. Note that you can use types only **once**, since access to components is type based. Thus, if you want to include components with the same type, wrap them into *structs* like so: 80 | 81 | ```C 82 | //vecs::Handle hx = system.Insert(5, 6); //compile error beacuse of two ints 83 | struct height_t { int i; }; 84 | using weight_t = vsty::strong_type_t>; 85 | vecs::Handle hx1 = system.Insert(height_t{5}, weight_t{6}); //compiles 86 | ``` 87 | In the second case, the Vienny Strong Type is used, which is a convenience wrapper needing explicit construction and allowing implicit conversion, integer value partitioning, default values, testing for NULL values, etc. See the VSTY library for its usage. The counter makes sure that internally always a new type is created. 88 | 89 | Entities always contain their handle as a component. Thus it is not possible to additionally insert components of type *Handle*. If you need to insert handles, wrap them into a struct. 90 | 91 | Do not forget to use type names that describe the intent of the new type, not its type itself. So if you need another integer for storing the height of a person, name it *height_t* rather than *myint_t*. You can check whether an entity still exists by calling *Exists(handle)*. You can get a reference to a *std::set* holding *std::size_t* representing the component types that a given entity has by calling *Types()*. You can check whether an entity has a specific component type *T* by calling *Has\(handle)*. You can erase an entity and all its components by calling *Erase(handle)*. You can also erase individual components *T1, T2, ...* by calling *Erase(handle)*. Note that it is perfectly fine to remove all components from an entity. This does not remove the entity itself, and you can afterwards add new components to it. Call *Clear()* to remove all entities from the registry. 92 | 93 | ```C 94 | vecs::Handle h1 = system.create(5); //create a new entity with one int component 95 | 96 | assert( system.Exists(h1) ); //check that it exists 97 | auto t1 = system.Types(h1); //get list of types (vector with type_index) 98 | assert( system.Has(h1) ); //check whether an entity has a component with a given type 99 | system.Erase(h1); //erase an entity 100 | assert( !system.Exists(h1) ); // 101 | 102 | vecs::Handle h2 = system.Insert(5, 6.9f, 7.3);; //create a new entity with int, float and double components 103 | system.Erase(h2); //erase integer and float components 104 | assert( !system.Has(h2) ); //check whether the components are gone 105 | assert( !system.Has(h2) ); 106 | assert( system.Has(h2) ); 107 | system.Erase(h2); //remove also the last component 108 | assert( system.Exists(h2) ); //check that the entity still exists 109 | 110 | assert( system.Size() > 0 ); 111 | system.Clear(); //clear the system 112 | assert( system.Size() == 0 ); 113 | ``` 114 | 115 | ## References 116 | 117 | You can get the current value of type *T* of an entity by calling *Get\(handle)*. Here *T* is neither a pointer nor a reference. 118 | 119 | Obtaining pure C++ references is not possible in VECS. Instead, you can get a reference object *Ref\* to a component by calling *Get(handle)*. Reference objects can be used like normal references. They track the component's location are react accordingly. 120 | However, if it accesses an erased entity or erased component, the program is aborted with an error. 121 | 122 | ```C 123 | vecs::Handle h2 = system.Insert(5, 6.9f, 7.3);; //create a new entity with int, float and double components 124 | auto value = system.Get(h2); //get Ref 125 | float f1 = value; //get value 126 | value = 10.0f; //set value 127 | auto c = system.Get(handle); //new component -> all components are moved but reference is still valid 128 | ``` 129 | 130 | If you specify more than one type, you can get a tuple holding the specified types or references (reference objects). You can easily access all component values by using C++17 *structured binding*. Calling *Get\(handle)* on a type *T* that is not yet part of the entity will also create an empty new component for the entity. 131 | 132 | ```C 133 | vecs::Handle h2 = system.Insert(5, 6.9f, 7.3);; //create a new entity with int, float and double components 134 | float value = system.Get(h2); //get float value directly 135 | std::tuple tup = system.Get(h2); //returns a std::tuple 136 | float value2 = std::get(tup); //access the value from the tuple 137 | auto [fvalue, dvalue] = system.Get(h2); //structured binding. fvalue is now a Ref to the component!! 138 | auto cc = system.Get(h2); //Create char component and return a reference to it (note the &)! 139 | cc = 'A'; //can change values 140 | auto dd = system.Get(h2); //the value has changed 141 | ``` 142 | 143 | You can update the value of a component by calling *Put(handle, values...)*. You can call *Put(handle, values...)* using the new values directly, or by using a tuple as single value parameter. This way, you can reuse the same tuple that you previously extracted by calling *Get(handle)*. 144 | 145 | ```C 146 | vecs::Handle h2 = system.Insert(5, 6.9f, 7.3); //create a new entity with int, float and double components 147 | assert( system.Get(h2) == 6.9f && system.Get(h2) == 7.3 ); //check float and double values 148 | system.Put(h2, 66.9f, 77.3); //change float and double values 149 | assert( system.Get(h2) == 66.9f && system.Get(h2) == 77.3 ); //check new values 150 | 151 | std::tuple tup = system.Get(h2); //returns a std::tuple 152 | assert( std::get(tup) == 66.9f && std::get(tup) == 77.3); //access the values from the tuple 153 | std::get(tup) == 666.9f; //change tuple values 154 | std::get(tup) == 777.3; 155 | system.Put(h2, tup); //store new values in the ECS 156 | auto [fvalue, dvalue] = system.Get(h2); //access the same entity 157 | assert( fvalue == 666.9f && dvalue == 777.3); //check the values 158 | 159 | auto h2 = system.Insert(5, 6.9f, 7.3); 160 | assert( system.Exists(h2) ); 161 | auto t2 = system.Types(h2); 162 | ``` 163 | 164 | Of course you can always change component values by getting and changing references calling *Get(handle)*, which returns a tuple with Ref objects. Calling it on a new type will again create this component for the entity and set its value accordingly. 165 | 166 | ```C 167 | auto [v2a, v2b] = system.Get(h2); 168 | auto [v3a, v3b] = system.Get(h2); //tuple with Refs 169 | v3a = 100.0f; 170 | v3b = 101.0; 171 | auto [v4a, v4b] = system.Get(h2); //check new values 172 | ``` 173 | 174 | *std::string* can be included directly, *const char** must be wrapped into a struct. 175 | 176 | ```C 177 | std::string s = "AAA"; 178 | struct T1 { 179 | char* m_str; 180 | }; 181 | system.Put(h2, s, T1{"BBB"}); // 182 | auto [ee, ff] = system.Get(h2); // 183 | ``` 184 | 185 | ## Strong Types in Ref\ Objects 186 | If you use *VSTY* strong types, this means another onion layer of containment for the true value. Accessing it inside a *Ref\* object follows some rules. The call *operator()* now returns the strong type *value*, not the strong type itself. This way, you only need one () instead of two. The *Value()* function does the same, while the *Get()* function returns the *strong type*, not the value of the strong type. If you want to read or write struct members, the compiler may force you to use teh call operator(), in order to make sure that you nmean the value of the Ref\ object. 187 | 188 | 189 | ```C 190 | struct test_struct { 191 | int i; 192 | float f; 193 | }; 194 | using strong_struct = vsty::strong_type_t>; 195 | using strong_int = vsty::strong_type_t>; 196 | 197 | ... 198 | 199 | strong_int si{5}; 200 | strong_struct ss{{10, 6.9f}}; 201 | auto handle = system.Insert(si, ss); 202 | check( system.Has(handle) ); 203 | auto [rsi, rss] = system.Get(handle); 204 | int i = rsi; //implicit type conversion 205 | strong_struct ss2 = rss; 206 | rss().i = 100; //use call operator() 207 | check( system.Get(handle)().i == 100 ); 208 | rss.Value().i = 101; //use Value() for strong type 209 | check( system.Get(handle)().i == 101 ); 210 | rss().f = 101.0f; 211 | check( system.Get(handle)().f == 101.0f ); 212 | rss.Value().f = 102.0f; 213 | check( system.Get(handle)().f == 102.0f ); 214 | auto mewss = system.Get(handle); 215 | mewss.Value().i = 103; 216 | check( system.Get(handle)().i == 103 ); 217 | mewss.Get() = {104, 204.0f}; //access to strong type 218 | check( system.Get(handle)().i == 104 ); 219 | check( system.Get(handle)().f == 204.0f ); 220 | ``` 221 | 222 | 223 | ## Iteration 224 | 225 | You can iterate over all components of a given type creating a *View*. The view covers all entities that hold all components of the specified types, and you can iterate over them using a standard C++ range based for loop. The following example creates views with one or three types and then iterates over all entities having these components. In the second loop, we get references and thus could also update the component values by iterating over them. 226 | 227 | ```C 228 | for( auto handle : system.GetView() ) { //iterate over ALL entities 229 | std::cout << "Handle: "<< handle << std::endl; 230 | } 231 | 232 | for( auto [handle, i, f] : system.GetView() ) { 233 | std::cout << "Handle: "<< handle << " int: " << i << " float: " << f << std::endl; 234 | i = 10; //write new value 235 | } 236 | ``` 237 | 238 | Note that if you want the handle of the entity you are currently accessing, you have to explicitly include the *vecs::Handle* type also into the view. Also note that trying to get a handle reference results in a compile error. 239 | 240 | ```C 241 | for( auto [handle, i, f] : system.GetView() ) { //works 242 | std::cout << "Handle: "<< handle << " int: " << i << " float: " << f << std::endl; 243 | } 244 | 245 | for( auto [handle, i, f] : system.GetView() ) { //compile error 246 | std::cout << "Handle: "<< handle << " int: " << i << " float: " << f << std::endl; 247 | } 248 | ``` 249 | 250 | Inside the for loop you can do everything as long as VECS is running in *sequential mode*. Nevertheless, of course erasing entities might result in crashes if systems still try to access them. Systems can check if entities still exist using the *Exists(handle)* function, this also works for Ref\ objects. VECS does not use C++ *std::optional* intentionally since accessing erased entities should never occur which lies in the responsibility of the programmer. 251 | 252 | ## Tags 253 | 254 | Entities can be decorated with tags, which are simply *uint64_t* numbers. You can add tags to an entity with the function *AddTag()*, you can remove tags using *EraseTags()*. The *GetView()* function allows for zero, 1 or 2 parameters. If one parameter is given, then this is reference to a *std::vector* having a positive tag list, i.e., only those entities that have these tags attached will be iterated over. If a second parameter is given, then this is a negative tag list, i.e., only those entities that do not have these tags will be iterated over. 255 | 256 | ```C 257 | auto handle1 = system.Insert(5, 6.9f, 7.3); 258 | auto handle2 = system.Insert(6, 7.9f, 8.3); 259 | auto handle3 = system.Insert(7, 8.9f, 9.3); 260 | system.Print(); 261 | system.AddTags(handle1, 1ul, 2ul, 3ul); 262 | system.AddTags(handle2, 1ul, 3ul); 263 | system.AddTags(handle3, 2ul, 3ul); 264 | system.Print(); 265 | auto tags = system.Types(handle1); 266 | assert( tags.size() == 7 ); 267 | 268 | for( auto handle : system.template GetView(std::vector{1ul}) ) { 269 | std::cout << "Handle (yes 1): "<< handle << std::endl; //finds two entities 270 | } 271 | 272 | for( auto handle : system.template GetView(std::vector{1ul}, std::vectorul}) ) { 273 | std::cout << "Handle (yes 1 no 2): "<< handle << std::endl; //finds only one entity 274 | } 275 | 276 | auto handle = system.Insert(5, 6.9f, 7.3); 277 | system.AddTags(handle, 1ul, 2ul, 3ul); 278 | system.EraseTags(handle, 1ul); 279 | 280 | ``` 281 | 282 | ## Parallel Usage 283 | Parallel usage at this point is not possible. Make sure to externally synchronize VECS. 284 | 285 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | rem cmake -S . -Bbuild -G "Visual Studio 17 2022" -T ClangCL -A x64 2 | cmake -S . -Bbuild -A x64 3 | cd build 4 | cmake --build . --config Release 5 | ctest -C Release 6 | cmake --build . --config Debug 7 | ctest -C Release 8 | cd .. 9 | -------------------------------------------------------------------------------- /documentation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML Meta Tag 5 | 6 | 7 | 8 |

-

9 | 10 | 11 | -------------------------------------------------------------------------------- /include/VECS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace vecs { 13 | 14 | using Mutex_t = std::shared_mutex; ///< Shared mutex type 15 | using namespace std::chrono_literals; 16 | 17 | template 18 | concept VecsPOD = (!std::is_reference_v && !std::is_pointer_v); 19 | 20 | //---------------------------------------------------------------------------------------------- 21 | //Convenience 22 | 23 | template 24 | struct is_std_vector : std::false_type {}; 25 | 26 | template 27 | struct is_std_vector> : std::true_type {}; 28 | 29 | template struct is_tuple : std::false_type {}; 30 | template struct is_tuple> : std::true_type {}; 31 | 32 | template 33 | struct Yes {}; 34 | 35 | template 36 | struct No {}; 37 | 38 | /// @brief Turn a type into a hash. 39 | /// @tparam T The type to hash. 40 | /// @return The hash of the type. 41 | template 42 | inline auto Type() -> std::size_t { 43 | return std::type_index(typeid(T)).hash_code(); 44 | } 45 | 46 | /// @brief Compute the hash of a list of hashes. If stored in a vector, make sure that hashes are sorted. 47 | /// @tparam T Container type of the hashes. 48 | /// @param hashes Reference to the container of the hashes. 49 | /// @return Overall hash made from the hashes. 50 | template 51 | inline size_t Hash( T& hashes ) { 52 | std::size_t seed = 0; 53 | if constexpr ( is_std_vector>::value ) { 54 | std::sort(hashes.begin(), hashes.end()); 55 | } 56 | for( auto& v : hashes ) { 57 | seed ^= v + 0x9e3779b9 + (seed<<6) + (seed>>2); 58 | } 59 | return seed; 60 | } 61 | 62 | template 63 | inline size_t Hash( T&& hashes ) { 64 | std::size_t seed = 0; 65 | if constexpr ( is_std_vector>::value ) { 66 | std::sort(hashes.begin(), hashes.end()); 67 | } 68 | for( auto& v : hashes ) { 69 | seed ^= v + 0x9e3779b9 + (seed<<6) + (seed>>2); 70 | } 71 | return seed; 72 | } 73 | } 74 | 75 | #if !defined(REGISTRYTYPE_SEQUENTIAL) || !defined(REGISTRYTYPE_PARALLEL) 76 | #define REGISTRYTYPE_SEQUENTIAL 77 | #endif 78 | 79 | #ifdef REGISTRYTYPE_SEQUENTIAL 80 | using Size_t = std::size_t; 81 | #else 82 | using Size_t = std::atomic; 83 | #endif 84 | 85 | #include 86 | #include 87 | #include "VECSHandle.h" 88 | #include "VECSMutex.h" 89 | #include "VECSVector.h" 90 | #include "VECSSlotMap.h" 91 | #include "VECSArchetype.h" 92 | #include "VECSRegistry.h" 93 | -------------------------------------------------------------------------------- /include/VECSArchetype.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vecs { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | //Archetype 7 | 8 | /// @brief An archetype of entities with the same components. 9 | /// All entities that have the same components are stored in the same archetype. 10 | /// The components are stored in the component maps. Note that the archetype class is not templated, 11 | /// but some methods including a constructor are templated. Thus the class knows only type indices 12 | /// of its components, not the types themselves. 13 | class Archetype { 14 | 15 | public: 16 | 17 | /// @brief A pair of an archetype and an index. This is stored in the slot map. 18 | struct ArchetypeAndIndex { 19 | Archetype* m_arch; //pointer to the archetype 20 | size_t m_index; //index of the entity in the archetype 21 | }; 22 | 23 | /// @brief Constructor, creates the archetype. 24 | Archetype() { 25 | AddComponent(); //insert the handle 26 | } 27 | 28 | /// @brief Insert a new entity with components to the archetype. 29 | /// @tparam ...Ts Value types of the components. 30 | /// @param handle The handle of the entity. 31 | /// @param ...values The values of the components. 32 | /// @return The index of the entity in the archetype. 33 | template 34 | size_t Insert(Handle handle, Ts&& ...values ) { 35 | assert( m_maps.size() == sizeof...(Ts) + 1 ); 36 | assert( (m_maps.contains(Type()) && ...) ); 37 | (AddValue( std::forward(values) ), ...); //insert all components, get index of the handle 38 | return AddValue( handle ); //insert the handle 39 | } 40 | 41 | /// @brief Get referece to the types of the components. 42 | /// @return A reference to the container of the types. 43 | [[nodiscard]] auto& Types() { 44 | return m_types; 45 | } 46 | 47 | /// @brief Test if the archetype has a component. 48 | /// @param ti Hash of the type index of the component. 49 | /// @return true if the archetype has the component, else false. 50 | bool Has(const size_t ti) { 51 | return m_types.contains(ti); 52 | } 53 | 54 | /// @brief Get component value of an entity. 55 | /// @tparam T The type of the component. 56 | /// @param archIndex The index of the entity in the archetype. 57 | /// @return The component value. 58 | template 59 | [[nodiscard]] auto Get(size_t archIndex) -> U& { 60 | using T = std::decay_t; 61 | assert( m_maps.contains(Type()) ); 62 | assert( m_maps[Type()]->size() > archIndex ); 63 | return (*Map())[archIndex]; //Map() decays the type 64 | } 65 | 66 | /// @brief Get component values of an entity. 67 | /// @tparam ...Ts Types of the components to get. 68 | /// @param handle Handle of the entity. 69 | /// @return A tuple of the component values. 70 | template 71 | requires (sizeof...(Ts) > 1) 72 | [[nodiscard]] auto Get(size_t archIndex) -> std::tuple { 73 | assert( (m_maps.contains(Type()) && ...) ); 74 | //assert( (m_maps[Type()]->size() > archIndex && ...) ); 75 | return std::tuple&...>{ (*Map>())[archIndex]... }; 76 | } 77 | 78 | /// @brief Put component values to an entity. 79 | /// @tparam ...Ts Types of the components to put. 80 | /// @param archIndex The index of the entity in the archetype. 81 | /// @param ...vs The component values. 82 | template 83 | void Put(size_t archIndex, Ts&& ...vs) { 84 | assert( (m_maps.contains(Type()) && ...) ); 85 | auto fun = [&](T&& v){ (*Map>())[archIndex] = std::forward(v); }; 86 | (fun.template operator()(std::forward(vs)), ... ); 87 | } 88 | 89 | /// @brief Erase an entity 90 | /// @param index The index of the entity in the archetype. 91 | /// @param slotmaps The slot maps vector of the registry. 92 | auto Erase(size_t index) -> Handle { 93 | return Erase2(index); 94 | } 95 | 96 | /// @brief Move components from another archetype to this one. In the other archetype, 97 | /// the last entity is moved to the erased one. This might result in a reindexing of the moved entity in the slot map. 98 | /// @param other The other archetype. 99 | /// @param other_index The index of the entity in the other archetype. 100 | /// @return A pair of the index of the new entity in this archetype and the handle of the moved entity. 101 | auto Move( Archetype& other, size_t other_index ) -> std::pair { 102 | for( auto& ti : m_types ) { //go through all maps 103 | if( m_maps.contains(ti) ) { 104 | if( other.m_maps.contains(ti) ) { 105 | m_maps[ti]->copy(other.Map(ti), other_index); //insert the new value 106 | } else { 107 | m_maps[ti]->push_back(); //insert an empty value 108 | } 109 | } 110 | } 111 | ++m_changeCounter; 112 | return { m_maps[Type()]->size() - 1, other.Erase2(other_index) }; 113 | } 114 | 115 | /// @brief Clone the archetype. 116 | /// @param other The archetype to clone. 117 | /// @param ignore Ignore these types. 118 | void Clone(Archetype& other, auto&& ignore) { 119 | for( auto& ti : other.m_types ) { //go through all maps 120 | if(std::find( ignore.begin(), ignore.end(), ti) != ignore.end()) { continue; } 121 | m_types.insert(ti); //add the type to the list, could be a tag 122 | if( other.m_maps.contains(ti) ) { 123 | m_maps[ti] = other.Map(ti)->clone(); //make a component map like this one 124 | } 125 | } 126 | } 127 | 128 | /// @brief Get the number of entites in this archetype. 129 | /// @return The number of entities. 130 | size_t Size() { 131 | return m_maps[Type()]->size() - m_gaps.size(); 132 | } 133 | 134 | /// @brief Get the number of rows in this archetype. 135 | /// This is the number of entities plus the number of gaps. 136 | /// @return The number of rows. 137 | size_t Number() { 138 | return m_maps[Type()]->size(); 139 | } 140 | 141 | /// @brief Clear the archetype. 142 | void Clear() { 143 | for( auto& map : m_maps ) { 144 | map.second->clear(); 145 | } 146 | ++m_changeCounter; 147 | } 148 | 149 | /// @brief Print the archetype. 150 | void Print() { 151 | std::cout << "Archetype: " << Hash(m_types) << std::endl; 152 | for( auto ti : m_types ) { 153 | std::cout << "Type: " << ti << " "; 154 | } 155 | std::cout << std::endl; 156 | for( auto& map : m_maps ) { 157 | std::cout << "Map: "; 158 | map.second->print(); 159 | std::cout << std::endl; 160 | } 161 | std::cout << "Entities: "; 162 | auto map = Map(); 163 | for( auto handle : *map ) { 164 | std::cout << handle << " "; 165 | } 166 | std::cout << std::endl << std::endl; 167 | } 168 | 169 | /// @brief Validate the archetype. Make sure all maps have the same size. 170 | void Validate() { 171 | for( auto& map : m_maps ) { 172 | assert( map.second->size() == m_maps[Type()]->size() ); 173 | } 174 | } 175 | 176 | /// @brief Get the change counter of the archetype. It is increased when a change occurs 177 | /// that might invalidate a Ref object, e.g. when an entity is moved to another archetype, or erased. 178 | auto GetChangeCounter() -> size_t { 179 | return m_changeCounter; 180 | } 181 | 182 | /// @brief Get the mutex of the archetype. 183 | /// @return Reference to the mutex. 184 | [[nodiscard]] auto GetMutex() -> Mutex_t& { 185 | return m_mutex; 186 | } 187 | 188 | void AddType(size_t ti) { 189 | assert( !m_types.contains(ti) ); 190 | m_types.insert(ti); //add the type to the list 191 | }; 192 | 193 | /// @brief Add a new component to the archetype. 194 | /// @tparam T The type of the component. 195 | template 196 | void AddComponent() { 197 | using T = std::decay_t; //remove pointer or reference 198 | size_t ti = Type(); 199 | assert( !m_types.contains(ti) ); 200 | m_types.insert(ti); //add the type to the list 201 | m_maps[ti] = std::make_unique>(); //create the component map 202 | }; 203 | 204 | /// @brief Add a new component value to the archetype. 205 | /// @param v The component value. 206 | /// @return The index of the component value. 207 | template 208 | auto AddValue( U&& v ) -> size_t { 209 | using T = std::decay_t; 210 | return m_maps[Type()]->push_back(std::forward(v)); //insert the component value 211 | }; 212 | 213 | auto AddEmptyValue( size_t ti ) -> size_t { 214 | return m_maps[ti]->push_back(); //insert the component value 215 | }; 216 | 217 | /// @brief Get the map of the components. 218 | /// @tparam T The type of the component. 219 | /// @return Pointer to the component map. 220 | template 221 | auto Map() -> Vector>* { 222 | using T = std::decay_t; 223 | auto it = m_maps.find(Type()); 224 | assert(it != m_maps.end()); 225 | return static_cast*>(it->second.get()); 226 | } 227 | 228 | /// @brief Get the data of the components. 229 | /// @param ti Type index of the component. 230 | /// @return Pointer to the component map base class. 231 | auto Map(size_t ti) -> VectorBase* { 232 | auto it = m_maps.find(ti); 233 | assert(it != m_maps.end()); 234 | return it->second.get(); 235 | } 236 | 237 | private: 238 | 239 | /// @brief Erase an entity. To ensure thet consistency of the entity indices, the last entity is moved to the erased one. 240 | /// This might result in a reindexing of the moved entity in the slot map. Thus we need a ref to the slot map 241 | /// @param index The index of the entity in the archetype. 242 | /// @return The handle of the moved last entity. 243 | auto Erase2(size_t index) -> Handle { 244 | size_t last{index}; 245 | ++m_changeCounter; 246 | if( m_iteratingArchetype == this && index <= m_iteratingIndex ) { //delayed erasure 247 | m_gaps.push_back(index); 248 | (*Map())[index] = Handle{}; //invalidate the handle 249 | return Handle{}; 250 | } 251 | for( auto& it : m_maps ) { last = it.second->erase(index); } //Erase from the component map 252 | return index < last ? (*Map())[index] : Handle{}; //return the handle of the moved entity 253 | } 254 | 255 | using Map_t = std::unordered_map>; 256 | Mutex_t m_mutex; //mutex for thread safety 257 | Size_t m_changeCounter{0}; //changes invalidate references 258 | std::set m_types; //types of components 259 | Map_t m_maps; //map from type index to component data 260 | 261 | public: 262 | //Parallelization strategy (not yet implemented): 263 | //- When iterating over an archetype, the archetype is locked for READING. 264 | //- If an entity E should be erased (erase, add or erase component), then 265 | // - the archetype is released for reading and locked for WRITING. 266 | // - If E is AFTER the current entity C, then the last entity L is moved over E, release write, lock read. 267 | // - If E is BEFORE or EQUAL the current entity C, filling the gap is DELAYED. Instead, the index if E 268 | // is stored in a list of delayed entities. When the iteration is finished, the gaps are closed. 269 | // Also the archetype stays in write lock until the end of the iteration. 270 | inline static thread_local Archetype* m_iteratingArchetype{nullptr}; //for iterating over archetypes 271 | inline static thread_local size_t m_iteratingIndex{std::numeric_limits::max()}; //current iterator index 272 | inline static thread_local std::vector m_gaps{}; //gaps from previous erasures that must be filled 273 | }; //end of Archetype 274 | 275 | } //namespace vecs2 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /include/VECSHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vecs { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | //Handles 7 | 8 | /// @brief A handle for an entity or a component. 9 | template 10 | struct HandleT { 11 | using type_t = typename vsty::strong_type_t, 12 | std::integral_constant::max()>>; ///< Strong type for the handle type. 13 | 14 | public: 15 | HandleT() = default; ///< Default constructor. 16 | 17 | HandleT(size_t index, size_t version, size_t storageIndex=0) : 18 | m_value{std::numeric_limits::max()} { 19 | m_value.set_bits(index, 0, INDEX_BITS); 20 | m_value.set_bits(version, INDEX_BITS, VERSION_BITS); 21 | m_value.set_bits(storageIndex, INDEX_BITS + VERSION_BITS, STORAGE_BITS); 22 | }; 23 | 24 | HandleT(size_t v) : m_value{type_t{v}} {}; 25 | 26 | size_t GetIndex() const { return m_value.get_bits(0, INDEX_BITS); } 27 | size_t GetVersion() const { return m_value.get_bits(INDEX_BITS, VERSION_BITS); } 28 | size_t GetStorageIndex() const { return m_value.get_bits(INDEX_BITS + VERSION_BITS); } 29 | size_t GetVersionedIndex() const { return (GetVersion() << VERSION_BITS) + GetIndex(); } 30 | size_t GetValue() const { return m_value(); }; 31 | bool IsValid() const { return m_value != std::numeric_limits::max(); } 32 | HandleT& operator=(const HandleT& other) { m_value = other.m_value; return *this; } 33 | bool operator==(const HandleT& other) const { return GetIndex() == other.GetIndex() && GetVersion() == other.GetVersion(); } 34 | bool operator!=(const HandleT& other) const { return !(*this == other); } 35 | bool operator<(const HandleT& other) const { return GetIndex() < other.GetIndex(); } 36 | 37 | private: 38 | type_t m_value; ///< Strong type for the handle. 39 | }; 40 | 41 | using Handle = HandleT<32,24,8>; ///< Type of the handle. 42 | 43 | inline bool IsValid(const Handle& handle) { 44 | return handle.IsValid(); 45 | } 46 | 47 | } 48 | 49 | inline std::ostream& operator<<(std::ostream& os, const vecs::Handle& handle) { 50 | return os << "{" << handle.GetIndex() << ", " << handle.GetVersion() << ", " << handle.GetStorageIndex() << "}"; 51 | } 52 | -------------------------------------------------------------------------------- /include/VECSMutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vecs { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | //Mutexes and Locks 7 | 8 | const int LOCKGUARDTYPE_SEQUENTIAL = 0; 9 | const int LOCKGUARDTYPE_PARALLEL = 1; 10 | 11 | /// @brief An exclusive lock guard for a mutex, meaning that only one thread can lock the mutex at a time. 12 | /// A LockGuard is used to lock and unlock a mutex in a RAII manner. 13 | /// In case of two simultaneous locks, the mutexes are locked in the correct order to avoid deadlocks. 14 | /// @tparam LTYPE Type of the lock guard. 15 | template 16 | requires (LTYPE == LOCKGUARDTYPE_SEQUENTIAL || LTYPE == LOCKGUARDTYPE_PARALLEL) 17 | struct LockGuard { 18 | 19 | /// @brief Constructor for a single mutex. 20 | /// @param mutex Pointer to the mutex. 21 | LockGuard(Mutex_t* mutex) : m_mutex{mutex}, m_other{nullptr} { 22 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { 23 | if(mutex) m_mutex->lock(); 24 | } 25 | } 26 | 27 | /// @brief Constructor for two mutexes. This is necessary if an entity must be moved from one archetype to another, because 28 | /// the entity components change. In this case, two mutexes must be locked. 29 | /// @param mutex Pointer to the mutex. 30 | /// @param other Pointer to the other mutex. 31 | LockGuard(Mutex_t* mutex, Mutex_t* other) : m_mutex{mutex}, m_other{other} { 32 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { 33 | if(mutex && other) { 34 | std::min(m_mutex, m_other)->lock(); ///lock the mutexes in the correct order 35 | std::max(m_mutex, m_other)->lock(); 36 | } else if(m_mutex) m_mutex->lock(); 37 | } 38 | } 39 | 40 | /// @brief Destructor, unlocks the mutexes. 41 | ~LockGuard() { 42 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { 43 | if(m_mutex && m_other) { 44 | std::max(m_mutex, m_other)->unlock(); 45 | std::min(m_mutex, m_other)->unlock(); ///lock the mutexes in the correct order 46 | } else if(m_mutex) m_mutex->unlock(); 47 | } 48 | } 49 | 50 | Mutex_t* m_mutex{nullptr}; 51 | Mutex_t* m_other{nullptr}; 52 | }; 53 | 54 | /// @brief A lock guard for a shared mutex in RAII manner. Several threads can lock the mutex in shared mode at the same time. 55 | /// This is used to make sure that data structures are not modified while they are read. 56 | /// @tparam LTYPE Type of the lock guard. 57 | template 58 | requires (LTYPE == LOCKGUARDTYPE_SEQUENTIAL || LTYPE == LOCKGUARDTYPE_PARALLEL) 59 | struct LockGuardShared { 60 | 61 | /// @brief Constructor for a single mutex, locks the mutex. 62 | LockGuardShared(Mutex_t* mutex) : m_mutex{mutex} { 63 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { m_mutex->lock_shared(); } 64 | } 65 | 66 | /// @brief Destructor, unlocks the mutex. 67 | ~LockGuardShared() { 68 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { m_mutex->unlock_shared(); } 69 | } 70 | 71 | Mutex_t* m_mutex{nullptr}; ///< Pointer to the mutex. 72 | }; 73 | 74 | template 75 | requires (LTYPE == LOCKGUARDTYPE_SEQUENTIAL || LTYPE == LOCKGUARDTYPE_PARALLEL) 76 | struct UnlockGuardShared { 77 | 78 | /// @brief Constructor for a single mutex, locks the mutex. 79 | template 80 | UnlockGuardShared(T* ptr) { 81 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { 82 | if(ptr!=nullptr) { 83 | m_mutex = &ptr->GetMutex(); 84 | m_mutex->unlock_shared(); 85 | } 86 | } 87 | } 88 | 89 | /// @brief Destructor, unlocks the mutex. 90 | ~UnlockGuardShared() { 91 | if constexpr (LTYPE == LOCKGUARDTYPE_PARALLEL) { 92 | if(m_mutex!=nullptr) m_mutex->lock_shared(); 93 | } 94 | } 95 | 96 | Mutex_t* m_mutex{nullptr}; ///< Pointer to the mutex. 97 | }; 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /include/VECSRegistry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vecs { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | //Registry concepts and types 7 | 8 | template 9 | concept VecsDataType = ((vtll::unique>::value) && (!vtll::has_type< vtll::tl, Handle>::value)); 10 | 11 | template 12 | concept VecsView = ((vtll::unique>::value) && (sizeof...(Ts) > 0) && (!std::is_same_v && ...)); 13 | 14 | template 15 | concept VecsIterator = (vtll::unique>::value); 16 | 17 | template requires VecsIterator class Iterator; 18 | template requires VecsView class View; 19 | 20 | //---------------------------------------------------------------------------------------------- 21 | //Registry 22 | 23 | 24 | /// @brief A registry for entities and components. 25 | class Registry { 26 | 27 | /// @brief Entry for the seach cache 28 | struct TypeSetAndHash { 29 | std::set m_types; //set of types that have been searched for 30 | size_t m_hash; //hash of the set 31 | }; 32 | 33 | template 34 | struct SlotMapAndMutex { 35 | SlotMap m_slotMap; 36 | Mutex_t m_mutex; 37 | SlotMapAndMutex( uint32_t storageIndex, uint32_t bits ) : m_slotMap{storageIndex, bits}, m_mutex{} {}; 38 | SlotMapAndMutex( const SlotMapAndMutex& other ) : m_slotMap{other.m_slotMap}, m_mutex{} {}; 39 | }; 40 | 41 | #ifdef REGISTRYTYPE_SEQUENTIAL 42 | using NUMBER_SLOTMAPS = std::integral_constant; 43 | #else 44 | using NUMBER_SLOTMAPS = std::integral_constant; 45 | #endif 46 | 47 | using Slot_t = typename SlotMap::Slot; 48 | using SlotMaps_t = std::vector>; 49 | using HashMap_t = std::map>; 50 | 51 | public: 52 | 53 | //---------------------------------------------------------------------------------------------- 54 | 55 | template 56 | requires (!std::is_reference_v) 57 | class Ref { 58 | 59 | using T = std::decay_t; 60 | 61 | public: 62 | Ref() = default; 63 | Ref(Handle handle, Slot_t& slot) : m_handle{handle}, m_slot{&slot}, m_archetype{slot.m_value.m_arch} {} 64 | Ref(const Ref& other) : m_handle{other.m_handle}, m_slot{other.m_slot}, m_archetype{other.m_archetype} {} 65 | 66 | bool IsValid() { return m_slot != nullptr; } 67 | bool Exists() { return m_slot->m_version == m_handle.GetVersion(); } 68 | auto operator()() -> T& {return GetReference(); } 69 | auto operator=(T&& value) -> void { GetReference() = std::forward(value); } 70 | operator T&() { return GetReference(); } 71 | auto Value() -> T& { return GetReference(); } 72 | auto Get() -> T& { return GetReference(); } 73 | 74 | private: 75 | auto GetReference() -> T& { 76 | auto arch = m_slot->m_value.m_arch; 77 | auto index = m_slot->m_value.m_index; 78 | if( !m_slot || m_slot->m_version != m_handle.GetVersion() || ( arch != m_archetype && !arch->Has(Type()) ) ) { 79 | if( !arch->Has(Type()) ) { 80 | std::cout << "Reference to type " << typeid(std::declval()).name() << " invalidated because of adding or erasing a component or erasing an entity!" << std::endl; 81 | assert(false); 82 | exit(-1); 83 | } 84 | m_archetype = arch; 85 | } 86 | return (*arch->template Map())[index]; 87 | } 88 | 89 | Handle m_handle{}; 90 | Slot_t* m_slot{nullptr}; 91 | Archetype *m_archetype{nullptr}; 92 | }; 93 | 94 | //---------------------------------------------------------------------------------------------- 95 | 96 | template 97 | requires (!std::is_reference_v>) 98 | class Ref> { 99 | 100 | using T = vsty::strong_type_t; 101 | 102 | public: 103 | Ref() = default; 104 | Ref(Handle handle, Slot_t& slot) : m_handle{handle}, m_slot{&slot}, m_archetype{slot.m_value.m_arch} {} 105 | Ref(const Ref& other) : m_handle{other.m_handle}, m_slot{other.m_slot}, m_archetype{other.m_archetype} {} 106 | 107 | bool IsValid() { return m_slot != nullptr; } 108 | bool Exists() { return m_slot->m_version == m_handle.GetVersion(); } 109 | auto operator()() -> U& {return GetReference()(); } 110 | auto operator=(T&& value) -> void { GetReference()() = std::forward(value); } 111 | operator T&() { return GetReference(); } 112 | operator U&() { return GetReference()(); } 113 | auto Value() -> U& { return GetReference()(); } 114 | auto Get() -> T& { return GetReference(); } 115 | 116 | private: 117 | auto GetReference() -> T& { 118 | auto arch = m_slot->m_value.m_arch; 119 | auto index = m_slot->m_value.m_index; 120 | if( !m_slot || m_slot->m_version != m_handle.GetVersion() || ( arch != m_archetype && !arch->Has(Type()) ) ) { 121 | std::cout << "Reference to type " << typeid(std::declval()).name() << " invalidated because of adding or erasing a component or erasing an entity!" << std::endl; 122 | assert(false); 123 | exit(-1); 124 | } 125 | if( arch != m_archetype ) { 126 | m_archetype = arch; 127 | } 128 | return (*arch->template Map())[index]; 129 | } 130 | 131 | Handle m_handle{}; 132 | Slot_t* m_slot{nullptr}; 133 | Archetype *m_archetype{nullptr}; 134 | }; 135 | 136 | template 137 | using to_ref_t = std::conditional, Ref>, T>::type; 138 | 139 | 140 | //---------------------------------------------------------------------------------------------- 141 | 142 | /// @brief A structure holding a pointer to an archetype and the current size of the archetype. 143 | struct ArchetypeAndSize { 144 | Archetype* m_arch; //pointer to the archetype 145 | size_t m_size; //size of the archetype 146 | ArchetypeAndSize(Archetype* arch, size_t size) : m_arch{arch}, m_size{size} {} 147 | }; 148 | 149 | 150 | //---------------------------------------------------------------------------------------------- 151 | 152 | 153 | /// @brief Used for iterating over entity components. Iterators are created by a view. 154 | template 155 | class Iterator { 156 | 157 | public: 158 | /// @brief Iterator constructor saving a list of archetypes and the current index. 159 | /// @param arch List of archetypes. 160 | /// @param archidx First archetype index. 161 | Iterator( Registry& system, std::vector& arch, size_t archidx) 162 | : m_registry(system), m_archetypes{arch}, m_archidx{archidx}, m_entidx{0} { 163 | m_archidx>0 ? m_end = true : m_end = false; 164 | } 165 | 166 | /// @brief Copy constructor. 167 | Iterator(const Iterator& other) 168 | : m_registry{other.m_registry}, m_archetypes{other.m_archetypes}, m_archidx{other.m_archidx}, m_entidx{other.m_entidx} { 169 | 170 | } 171 | 172 | /// @brief Destructor, unlocks the archetype. 173 | ~Iterator() { 174 | if(m_end && m_archidx < m_archetypes.size()) { m_registry.FillGaps(m_archetypes[m_archidx].m_arch); } 175 | Archetype::m_iteratingArchetype = nullptr; 176 | } 177 | 178 | /// @brief Prefix increment operator. 179 | auto operator++() { 180 | if( m_archidx >= m_archetypes.size() ) { return *this; } 181 | ++m_entidx; 182 | Archetype::m_iteratingIndex = m_entidx; 183 | while( m_entidx >= m_archetypes[m_archidx].m_arch->Number() || m_entidx >= m_archetypes[m_archidx].m_size ) { 184 | m_entidx = 0; 185 | m_registry.FillGaps(m_archetypes[m_archidx].m_arch); 186 | ++m_archidx; 187 | if( m_archidx >= m_archetypes.size() ) { break; } 188 | } 189 | return *this; 190 | } 191 | 192 | /// @brief Access the content the iterator points to. 193 | auto operator*() { 194 | if(m_archidx < m_archetypes.size()) { 195 | Archetype::m_iteratingArchetype = m_archetypes[m_archidx].m_arch; 196 | Archetype::m_iteratingIndex = m_entidx; 197 | } 198 | 199 | auto tup = std::make_tuple( Get()... ); 200 | if constexpr (sizeof...(Ts) == 1) { return std::get<0>(tup); } 201 | else return tup; 202 | } 203 | 204 | /// @brief Compare two iterators. 205 | auto operator!=(const Iterator& other) -> bool { 206 | return (m_archidx != other.m_archidx) || (m_entidx != other.m_entidx); 207 | } 208 | 209 | private: 210 | 211 | template 212 | requires (!std::is_reference_v) 213 | auto Get() -> T { 214 | return (*m_archetypes[m_archidx].m_arch->template Map())[m_entidx]; 215 | } 216 | 217 | template 218 | requires std::is_reference_v 219 | auto Get() -> to_ref_t { 220 | auto arch = m_archetypes[m_archidx].m_arch; 221 | Handle handle = (*arch->template Map())[m_entidx]; 222 | return to_ref_t( handle, m_registry.GetSlot(handle)); 223 | } 224 | 225 | Registry& m_registry; ///< Reference to the registry system. 226 | Vector* m_mapHandle{nullptr}; ///< Pointer to the comp map holding the handle of the current archetype. 227 | std::vector& m_archetypes; ///< List of archetypes. 228 | size_t m_end{false}; ///< True if this is the end iterator. 229 | size_t m_archidx{0}; ///< Index of the current archetype. 230 | size_t m_entidx{0}; ///< Index of the current entity. 231 | }; //end of Iterator 232 | 233 | 234 | //---------------------------------------------------------------------------------------------- 235 | 236 | /// @brief A view of entities with specific components. 237 | /// @tparam ...Ts The types of the components. 238 | template 239 | class View { 240 | 241 | public: 242 | View(Registry& system, HashMap_t& map, auto&& tagsYes, auto&& tagsNo ) : 243 | m_system{system}, m_map(map), m_tagsYes{tagsYes}, m_tagsNo{tagsNo} { 244 | } ///< Constructor. 245 | 246 | /// @brief Get an iterator to the first entity. 247 | /// The archetype is locked in shared mode to prevent changes. 248 | /// @return Iterator to the first entity. 249 | auto begin() { 250 | m_archetypes.clear(); 251 | for( auto& map : m_map ) { //go through all archetypes 252 | auto arch = map.second.get(); 253 | if( arch->Size() == 0 ) { continue; } //skip empty archetypes 254 | bool hasTypes = (arch->Has(Type()) && ...); //should have all types 255 | bool hasAllTagsYes = true; //should have all tags 256 | bool hasNoTagsNo = true; //should not have any of these tags 257 | for( auto& tag : m_tagsYes ) { if( !arch->Has(tag) ) { hasAllTagsYes = false; break; } } 258 | for( auto& tag : m_tagsNo ) { if( arch->Has(tag) ) { hasNoTagsNo = false; break; } } 259 | if( hasTypes && hasAllTagsYes && hasNoTagsNo ) { //all conditions met 260 | m_archetypes.push_back({arch, arch->Size()}); 261 | } 262 | } 263 | return Iterator{m_system, m_archetypes, 0}; 264 | } 265 | 266 | /// @brief Get an iterator to the end of the view. 267 | auto end() { 268 | return Iterator{m_system, m_archetypes, m_archetypes.size()}; 269 | } 270 | 271 | private: 272 | 273 | Registry& m_system; ///< Reference to the registry system. 274 | std::vector m_tagsYes; ///< List of tags that must be present. 275 | std::vector m_tagsNo; ///< List of tags that must not be present. 276 | HashMap_t& m_map; ///< List of archetypes. 277 | std::vector m_archetypes; ///< List of archetypes. 278 | }; //end of View 279 | 280 | 281 | //---------------------------------------------------------------------------------------------- 282 | 283 | template friend class Iterator; 284 | 285 | Registry() { 286 | m_slotMaps.reserve(NUMBER_SLOTMAPS::value); //resize the slot storage 287 | for( uint32_t i = 0; i < NUMBER_SLOTMAPS::value; ++i ) { 288 | m_slotMaps.emplace_back( SlotMapAndMutex{ i, 6 } ); 289 | } 290 | }; 291 | 292 | ~Registry() = default; ///< Destructor. 293 | 294 | /// @brief Get the number of entities in the system. 295 | /// @return The number of entities. 296 | size_t Size() { 297 | return m_size; 298 | } 299 | 300 | /// @brief Create an entity with components. 301 | /// @tparam ...Ts The types of the components. 302 | /// @param ...component The new values. 303 | /// @return Handle of new entity. 304 | template 305 | requires ((sizeof...(Ts) > 0) && (vtll::unique>::value) && !vtll::has_type< vtll::tl, Handle>::value) 306 | [[nodiscard]] auto Insert( Ts&&... component ) -> Handle { 307 | size_t slotMapIndex = GetNewSlotmapIndex(); 308 | auto [handle, slot] = m_slotMaps[slotMapIndex].m_slotMap.Insert( {nullptr, 0} ); //get a slot for the entity 309 | slot.m_value.m_arch = GetArchetype(nullptr, {}, {}); 310 | slot.m_value.m_index = slot.m_value.m_arch->Insert( handle, std::forward(component)... ); //insert the entity into the archetype 311 | ++m_size; 312 | return handle; 313 | } 314 | 315 | /// @brief Test if an entity exists. 316 | /// @param handle The handle of the entity. 317 | /// @return true if the entity exists, else false. 318 | bool Exists(Handle handle) { 319 | auto& slot = GetSlot(handle); 320 | return slot.m_version == handle.GetVersion(); 321 | } 322 | 323 | /// @brief Test if an entity has a component. 324 | /// @tparam T The type of the component. 325 | /// @param handle The handle of the entity. 326 | /// @return true if the entity has the component, else false. 327 | template 328 | bool Has(Handle handle) { 329 | assert(Exists(handle)); 330 | auto arch = GetArchetypeAndIndex(handle).m_arch; 331 | return arch->Has(Type()); 332 | } 333 | 334 | /// @brief Test if an entity has a tag. 335 | /// @param handle The handle of the entity. 336 | /// @param tag The tag to test. 337 | /// @return true if the entity has the tag, else false. 338 | bool Has(Handle handle, size_t ti) { 339 | assert(Exists(handle)); 340 | auto arch = GetArchetypeAndIndex(handle).m_arch; 341 | return arch->Has(ti); 342 | } 343 | 344 | /// @brief Get the types of the components of an entity. 345 | /// @param handle The handle of the entity. 346 | /// @return A vector of type indices of the components. 347 | auto Types(Handle handle) { 348 | assert(Exists(handle)); 349 | auto arch = GetArchetypeAndIndex(handle).m_arch; 350 | return arch->Types(); 351 | } 352 | 353 | /// @brief Get a component value of an entity. 354 | /// @tparam T The type of the component. 355 | /// @param handle The handle of the entity. 356 | /// @return The component value or reference to it. 357 | template 358 | auto Get(Handle handle) -> to_ref_t { 359 | return std::get<0>(Get2(handle)); 360 | } 361 | 362 | /// @brief Get component values of an entity. 363 | /// @tparam Ts The types of the components. 364 | /// @param handle The handle of the entity. 365 | /// @return A tuple of the component values. 366 | template 367 | requires (sizeof...(Ts)>1 && vtll::unique>::value && !vtll::has_type< vtll::tl, Handle&>::value) 368 | [[nodiscard]] auto Get(Handle handle) -> std::tuple...> { 369 | return Get2(handle); 370 | } 371 | 372 | /// @brief Put new component values to an entity. 373 | /// @tparam Ts The types of the components. 374 | /// @param handle The handle of the entity. 375 | /// @param v The new values in a tuple 376 | template 377 | requires (vtll::unique>::value && !vtll::has_type< vtll::tl...>, Handle>::value) 378 | void Put(Handle handle, std::tuple& v) { 379 | Put2(handle, std::forward(std::get(v))...); 380 | } 381 | 382 | /// @brief Put new component values to an entity. 383 | /// @tparam Ts The types of the components. 384 | /// @param handle The handle of the entity. 385 | /// @param ...vs The new values. 386 | template 387 | requires ((vtll::unique>::value) && !vtll::has_type< vtll::tl...>, Handle>::value) 388 | void Put(Handle handle, Ts&&... vs) { 389 | Put2(handle, std::forward(vs)...); 390 | } 391 | 392 | /// @brief Add tags to an entity. 393 | /// @tparam ...Ts The types of the tags. 394 | /// @param handle The handle of the entity. 395 | /// @param ...tags The tags to add. 396 | template 397 | requires (std::is_integral_v> && ...) 398 | void AddTags(Handle handle, Ts... tags) { 399 | AddTags(handle, std::vector{tags...}); 400 | } 401 | 402 | /// @brief Add tags to an entity. 403 | /// @param handle The handle of the entity. 404 | /// @param tags The tags to add. 405 | /// @param ...tags The tags to add. 406 | void AddTags(Handle handle, const std::vector&& tags) { 407 | auto& archAndIndex = GetArchetypeAndIndex(handle); 408 | auto oldArch = archAndIndex.m_arch; 409 | auto newArch = GetArchetype(oldArch, std::forward(tags), {}); 410 | Move(newArch, oldArch, archAndIndex); 411 | } 412 | 413 | /// @brief Erase tags from an entity. 414 | /// @tparam ...Ts The types of the tags. 415 | /// @param handle The handle of the entity. 416 | /// @param ...tags The tags to erase. 417 | template 418 | requires (std::is_integral_v> && ...) 419 | void EraseTags(Handle handle, Ts... tags) { 420 | EraseTags(handle, std::vector{tags...}); 421 | } 422 | 423 | /// @brief Erase tags from an entity. 424 | /// @tparam ...Ts The types of the tags. 425 | /// @param handle The handle of the entity. 426 | /// @param ...tags The tags to erase. 427 | void EraseTags(Handle handle, const std::vector&& tags) { 428 | auto& archAndIndex = GetArchetypeAndIndex(handle); 429 | auto oldArch = archAndIndex.m_arch; 430 | auto newArch = GetArchetype(oldArch, {}, std::forward(tags)); 431 | Move(newArch, oldArch, archAndIndex); 432 | } 433 | 434 | /// @brief Erase components from an entity. 435 | /// @tparam ...Ts The types of the components. 436 | /// @param handle The handle of the entity. 437 | template 438 | requires (vtll::unique>::value && !vtll::has_type< vtll::tl, Handle>::value) 439 | void Erase(Handle handle) { 440 | auto& archAndIndex = GetArchetypeAndIndex(handle); 441 | auto arch = archAndIndex.m_arch; 442 | assert( (arch->Has(Type()) && ...) ); 443 | auto newArch = GetArchetype(arch, {}, std::vector{Type()...}); 444 | Move(newArch, arch, archAndIndex); 445 | } 446 | 447 | /// @brief Erase an entity from the registry. 448 | /// @param handle The handle of the entity. 449 | void Erase(Handle handle) { 450 | auto& slot = GetSlot(handle); 451 | auto& archAndIndex = slot.m_value; 452 | ReindexMovedEntity(archAndIndex.m_arch->Erase(archAndIndex.m_index), archAndIndex.m_index); 453 | slot.m_version++; //invalidate the slot 454 | --m_size; 455 | } 456 | 457 | /// @brief Clear the registry by removing all entities. 458 | void Clear() { 459 | for( auto& arch : m_archetypes ) { arch.second->Clear(); } 460 | for( auto& slotmap : m_slotMaps ) { slotmap.m_slotMap.Clear(); } 461 | m_size = 0; 462 | } 463 | 464 | /// @brief Get a view of entities with specific components. 465 | /// @tparam ...Ts The types of the components. 466 | /// @return A view of the entity components 467 | template 468 | requires (vtll::unique>::value) 469 | [[nodiscard]] auto GetView(std::vector&& yes={}, std::vector&& no={}) -> View { 470 | return {*this, m_archetypes, std::forward>(yes), std::forward>(no),}; 471 | } 472 | 473 | /// @brief Print the registry. 474 | /// Print the number of entities and the archetypes. 475 | void Print() { 476 | std::cout << "-----------------------------------------------------------------------------------------------" << std::endl; 477 | std::cout << "Entities: " << Size() << std::endl; 478 | for( auto& it : m_archetypes ) { 479 | std::cout << "Archetype Hash: " << it.first << std::endl; 480 | it.second->Print(); 481 | } 482 | std::cout << std::endl << std::endl; 483 | } 484 | 485 | /// @brief Validate the registry. 486 | /// Make sure all archetypes have the same size in all component maps. 487 | void Validate() { 488 | for( auto& it : m_archetypes ) { 489 | auto arch = it.second.get(); 490 | arch->Validate(); 491 | } 492 | } 493 | 494 | /// @brief Get the mutex of the archetype. 495 | /// @return Reference to the mutex. 496 | [[nodiscard]] inline auto GetSlotMapMutex(size_t index) -> Mutex_t& { 497 | return m_slotMaps[index].m_mutex; 498 | } 499 | 500 | /// @brief Get the mutex of the archetype. 501 | /// @return Reference to the mutex. 502 | [[nodiscard]] inline auto GetMutex() -> Mutex_t& { 503 | return m_mutex; 504 | } 505 | 506 | /// @brief Swap two entities. 507 | /// @param h1 The handle of the first entity. 508 | /// @param h2 The handle of the second entity. 509 | /// @return true if the entities were swapped, else false. 510 | bool Swap( Handle h1, Handle h2 ) { 511 | return true; 512 | } 513 | 514 | /// @brief Fill gaps from previous erasures. 515 | // This is necessary when an entity is erased during iteration. The last entity is moved to the erased one 516 | // after Iteration is finished. This is triggered by the iterator. 517 | void FillGaps(Archetype* arch) { 518 | std::sort(arch->m_gaps.begin(), arch->m_gaps.end(), std::greater()); 519 | Archetype::m_iteratingArchetype = nullptr; 520 | for( auto& gap : arch->m_gaps ) { 521 | if(gap < arch->Number()) ReindexMovedEntity(arch->Erase(gap), gap); 522 | } 523 | arch->m_gaps.clear(); 524 | Archetype::m_iteratingArchetype = arch; 525 | } 526 | 527 | private: 528 | 529 | /// @brief Test if a type is in a container. 530 | /// @param container The container to search. 531 | /// @param hs The type hash to search for. 532 | /// @return true if the type is in the container, else false. 533 | bool ContainsType(auto&& container, size_t hs) { 534 | return std::ranges::find(container, hs) != container.end(); 535 | } 536 | 537 | /// @brief Add a type to a container, not yet in the container. 538 | /// @param container The container to add to. 539 | /// @param hs The type hash to add. 540 | void AddType(auto&& container, size_t hs) { 541 | if(!ContainsType( container, hs)) container.push_back(hs); 542 | } 543 | 544 | /// @brief Get the index of the entity in the archetype 545 | /// @param handle The handle of the entity. 546 | /// @return The index of the entity in the archetype. 547 | auto GetSlot( Handle handle ) -> Slot_t& { 548 | return m_slotMaps[handle.GetStorageIndex()].m_slotMap[handle]; 549 | } 550 | 551 | /// @brief Get the index of the entity in the archetype 552 | /// @param handle The handle of the entity. 553 | /// @return The index of the entity and the archetype. 554 | auto GetArchetypeAndIndex( Handle handle ) -> Archetype::ArchetypeAndIndex& { 555 | return GetSlot(handle).m_value; 556 | } 557 | 558 | /// @brief Get a new index of the slotmap for the current thread. 559 | /// @return New index of the slotmap. 560 | size_t GetNewSlotmapIndex() { 561 | m_slotMapIndex = (m_slotMapIndex + 1) & (NUMBER_SLOTMAPS::value - 1); 562 | return m_slotMapIndex; 563 | } 564 | 565 | /// @brief Create a list of type hashes 566 | /// @param arch The archetype types to use 567 | /// @param tags Use also these tag hashes 568 | /// @return A vector of type hashes 569 | template 570 | auto CreateTypeList(Archetype* arch, const std::vector&& tags, const std::vector&& ignore) -> std::vector { 571 | std::vector all{ tags.begin(), tags.end() }; 572 | (AddType(all, Type()), ...); 573 | if(arch) { for( auto type : arch->Types() ) { if(!ContainsType(ignore, type)) { AddType(all, type); } } } 574 | return all; 575 | } 576 | 577 | /// @brief Get an archetype with components. 578 | /// @tparam ...Ts The component types. 579 | /// @param arch Use the types of this archetype. 580 | /// @param tags Should have the tags of the entity. 581 | /// @return A pointer to the archetype. 582 | template 583 | auto GetArchetype(Archetype* arch, const std::vector&& tags, const std::vector&& ignore) -> Archetype* { 584 | size_t hs = Hash(CreateTypeList(arch, std::forward(tags), std::forward(ignore))); 585 | if( m_archetypes.contains( hs ) ) { return m_archetypes[hs].get(); } 586 | 587 | auto newArchUnique = std::make_unique(); 588 | auto newArch = newArchUnique.get(); 589 | if(arch) newArch->Clone(*arch, ignore); //clone old types/components and old tags 590 | auto fun = [&](){ if( !ContainsType(newArch->Types(), Type()) ) { newArch->template AddComponent(); } }; 591 | (fun.template operator()(), ...); 592 | for( auto tag : tags ) { 593 | if(!ContainsType(newArch->Types(), tag) && !ContainsType(ignore, tag)) { newArch->AddType(tag); } 594 | } //add new tags 595 | m_archetypes[hs] = std::move(newArchUnique); //store the archetype 596 | return newArch; 597 | } 598 | 599 | /// @brief If a entity is moved or erased, the last entity of the archetype is moved to the empty slot. 600 | /// This might result in a reindexing of the moved entity in the slot map. 601 | /// @param handle The handle of the moved entity. 602 | /// @param index The index of the entity in the archetype. 603 | void ReindexMovedEntity(Handle handle, size_t index) { 604 | if( !handle.IsValid() ) { return; } 605 | auto& archAndIndex = GetArchetypeAndIndex(handle); 606 | archAndIndex.m_index = index; 607 | } 608 | 609 | /// @brief Move an entity to a new archetype. 610 | /// @param newArch The new archetype. 611 | /// @param oldArch The old archetype. 612 | /// @param archAndIndex The archetype and index of the entity. 613 | void Move(Archetype* newArch, Archetype* oldArch, vecs::Archetype::ArchetypeAndIndex& archAndIndex) { 614 | auto [newIndex, movedHandle] = newArch->Move(*oldArch, archAndIndex.m_index); 615 | ReindexMovedEntity(movedHandle, archAndIndex.m_index); 616 | archAndIndex = { newArch, newIndex }; 617 | } 618 | 619 | /// @brief Get component values of an entity. 620 | /// @tparam Ts The types of the components. 621 | /// @param handle The handle of the entity. 622 | /// @return A tuple of the component values. 623 | template 624 | requires (vtll::unique>::value && !vtll::has_type< vtll::tl, Handle&>::value) 625 | [[nodiscard]] auto Get2(Handle handle) { 626 | auto& slot = GetSlot(handle); 627 | auto& archAndIndex = slot.m_value; // GetArchetypeAndIndex(handle); 628 | auto arch = archAndIndex.m_arch; 629 | if( (arch->Has(Type()) && ...) ) { return std::tuple...>{ Get3(handle, slot)... }; } 630 | auto newArch = GetArchetype(arch, {}, {}); 631 | Move(newArch, arch, archAndIndex); 632 | return std::tuple...>{ Get3(handle, slot)... }; 633 | } 634 | 635 | template 636 | requires (!std::is_reference_v) 637 | auto Get3(Handle handle, Slot_t& slot ) -> T { //Archetype* arch, size_t index) -> T { 638 | return slot.m_value.m_arch->template Get(slot.m_value.m_index); 639 | } 640 | 641 | template 642 | requires std::is_reference_v 643 | auto Get3(Handle handle, Slot_t& slot ) { //Archetype* arch, size_t index) { 644 | return Ref>(handle, slot) ; //arch->template Get(index), arch->template Get>(index)); 645 | } 646 | 647 | /// @brief Change the component values of an entity. 648 | /// @tparam ...Ts The types of the components. 649 | /// @param handle The handle of the entity. 650 | /// @param ...vs The new values. 651 | template 652 | void Put2(Handle handle, Ts&&... vs) { 653 | auto& archAndIndex = GetArchetypeAndIndex(handle); 654 | auto arch = archAndIndex.m_arch; 655 | if( (arch->Has(Type()) && ...) ) { arch->Put(archAndIndex.m_index, std::forward(vs)...); return; } 656 | auto newArch = GetArchetype(arch, {}, {}); 657 | Move(newArch, arch, archAndIndex); 658 | newArch->Put(archAndIndex.m_index, std::forward(vs)...); 659 | } 660 | 661 | Size_t m_size{0}; //number of entities 662 | SlotMaps_t m_slotMaps; //Slotmap array for entities. Each slot map has its own mutex. 663 | HashMap_t m_archetypes; //Mapping hash (from type hashes) to archetype 1:1. 664 | Mutex_t m_mutex; //mutex for reading and writing m_archetypes. 665 | inline static thread_local size_t m_slotMapIndex = NUMBER_SLOTMAPS::value - 1; //for new entities 666 | }; 667 | 668 | template 669 | using Ref = Registry::Ref; 670 | 671 | } //end of namespace vecs 672 | 673 | 674 | 675 | -------------------------------------------------------------------------------- /include/VECSSlotMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vecs { 4 | 5 | //---------------------------------------------------------------------------------------------- 6 | //Slot Maps 7 | 8 | /// @brief A slot map for storing a map from handle to archetype and index in the archetype. 9 | /// A slot map can never shrink. If an entity is erased, the slot is added to the free list. A handle holds an index 10 | /// to the slot map and a version counter. If the version counter of the slot is different from the version counter of the handle, 11 | /// the slot is invalid. 12 | /// @tparam T The value type of the slot map. 13 | template 14 | class SlotMap { 15 | 16 | public: 17 | /// @brief A slot in the slot map. 18 | struct Slot { 19 | int64_t m_nextFree; //index of the next free slot in the free list. 20 | size_t m_version; //version of the slot 21 | T m_value{}; //value of the slot 22 | 23 | Slot() = default; 24 | 25 | /// @brief Constructor, creates a slot. 26 | /// @param next Next free slot in the free list. 27 | /// @param version Version counter of the slot to avoid accessing erased slots. 28 | /// @param value Value stored in the slot. 29 | Slot(const int64_t& next, const size_t& version, T& value) : m_value{value} { 30 | m_nextFree = next; 31 | m_version = version; 32 | } 33 | 34 | Slot(const int64_t&& next, const size_t&& version, T&& value) : m_value{std::forward(value)} { 35 | m_nextFree = next; 36 | m_version = version; 37 | } 38 | 39 | /// @brief Copy operator. 40 | /// @param other The slot to copy. 41 | Slot& operator=( const Slot& other ) { 42 | m_nextFree = other.m_nextFree; 43 | m_version = other.m_version; 44 | m_value = other.m_value; 45 | return *this; 46 | } 47 | }; 48 | 49 | public: 50 | 51 | /// @brief Constructor, creates the slot map and prefills it with an empty list. 52 | /// @param size Size of the initial slot map. 53 | SlotMap(uint32_t storageIndex, int64_t bits) : m_storageIndex{storageIndex} { 54 | m_firstFree = 0; 55 | int64_t size = ((int64_t)1l << bits); 56 | for( int64_t i = 1; i <= size-1; ++i ) { //create the free list 57 | m_slots.push_back( Slot(int64_t{i}, size_t{0}, T{} ) ); 58 | } 59 | m_slots.push_back( Slot(int64_t{-1}, size_t{0}, T{}) ); //last slot 60 | } 61 | 62 | SlotMap( const SlotMap& other ) : m_storageIndex{other.m_storageIndex} { 63 | m_firstFree = 0; 64 | int64_t size = other.m_slots.size(); 65 | for( int64_t i = 1; i <= size-1; ++i ) { //create the free list 66 | m_slots.push_back( Slot( int64_t{i}, size_t{0}, T{} ) ); 67 | } 68 | m_slots.push_back( Slot(int64_t{-1}, size_t{0}, T{}) ); //last slot 69 | } 70 | 71 | ~SlotMap() = default; ///< Destructor. 72 | 73 | /// @brief Insert a value to the slot map. 74 | /// @param value The value to insert. 75 | /// @return A pair of the handle and reference to the slot. 76 | auto Insert(T& value) -> std::pair { 77 | auto [handle, slot] = Insert2(std::forward(value)); 78 | slot.m_value = value; 79 | return {handle, slot}; 80 | } 81 | 82 | auto Insert(T&& value) -> std::pair { 83 | auto [handle, slot] = Insert2(std::forward(value)); 84 | slot.m_value = std::forward(value); 85 | return {handle, slot}; 86 | } 87 | 88 | /// @brief Erase a value from the slot map. 89 | /// @param handle The handle of the value to erase. 90 | void Erase(Handle handle) { 91 | auto& slot = m_slots[handle.GetIndex()]; 92 | ++slot.m_version; //increment the version to invalidate the slot 93 | slot.m_nextFree = m_firstFree; 94 | m_firstFree = handle.GetIndex(); //add the slot to the free list 95 | --m_size; 96 | } 97 | 98 | /// @brief Get a value from the slot map. Do not assert versions here, could be used for writing! 99 | /// @param handle The handle of the value to get. 100 | /// @return Reference to the value. 101 | auto operator[](Handle handle) -> Slot& { 102 | return m_slots[handle.GetIndex()]; 103 | } 104 | 105 | /// @brief Get the size of the slot map. 106 | /// @return The size of the slot map. 107 | auto Size() const -> size_t { 108 | return m_size; 109 | } 110 | 111 | /// @brief Clear the slot map. This puts all slots in the free list. 112 | void Clear() { 113 | m_firstFree = 0; 114 | m_size = 0; 115 | size_t size = m_slots.size(); 116 | for( size_t i = 1; i <= size-1; ++i ) { 117 | m_slots[i-1].m_nextFree = i; 118 | m_slots[i-1].m_version++; 119 | } 120 | m_slots[size-1].m_nextFree = -1; 121 | m_slots[size-1].m_version++; 122 | } 123 | 124 | private: 125 | auto Insert2(const T&& value) -> std::pair { 126 | int64_t index = m_firstFree; 127 | Slot* slot = nullptr; 128 | if( index > -1 ) { 129 | slot = &m_slots[index]; 130 | m_firstFree = slot->m_nextFree; 131 | slot->m_nextFree = -1; 132 | } else { 133 | m_slots.push_back( Slot{ int64_t{-1}, size_t{0}, {} } ); 134 | index = m_slots.size() - 1; //index of the new slot 135 | slot = &m_slots[index]; 136 | } 137 | ++m_size; 138 | return { Handle{ (uint32_t)index, (uint32_t)slot->m_version, m_storageIndex}, *slot}; 139 | } 140 | 141 | size_t m_storageIndex{0}; ///< Index of the storage. 142 | size_t m_size{0}; ///< Size of the slot map. This is the size of the Vector minus the free slots. 143 | int64_t m_firstFree{-1}; ///< Index of the first free slot. If -1 then there are no free slots. 144 | Vector m_slots; ///< Container of slots. 145 | }; 146 | 147 | 148 | 149 | } 150 | -------------------------------------------------------------------------------- /include/VECSVector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace vecs { 5 | 6 | //---------------------------------------------------------------------------------------------- 7 | //Segmented Vector 8 | 9 | template class Vector; 10 | 11 | class VectorBase { 12 | 13 | public: 14 | VectorBase() = default; //constructor 15 | virtual ~VectorBase() = default; //destructor 16 | 17 | /// @brief Insert a new component value. 18 | /// @param v The component value. 19 | /// @return The index of the new component value. 20 | template 21 | auto push_back(U&& v) -> size_t { 22 | using T = std::decay_t; 23 | return static_cast*>(this)->push_back(std::forward(v)); 24 | } 25 | 26 | virtual auto push_back() -> size_t = 0; 27 | virtual auto pop_back() -> void = 0; 28 | virtual auto erase(size_t index) -> size_t = 0; 29 | virtual void copy(VectorBase* other, size_t from) = 0; 30 | virtual void swap(size_t index1, size_t index2) = 0; 31 | virtual auto size() const -> size_t = 0; 32 | virtual auto clone() -> std::unique_ptr = 0; 33 | virtual void clear() = 0; 34 | virtual void print() = 0; 35 | }; //end of VectorBase 36 | 37 | 38 | /// @brief A vector that stores elements in segments to avoid reallocations. The size of a segment is 2^segmentBits. 39 | template 40 | class Vector : public VectorBase { 41 | 42 | using Segment_t = std::shared_ptr>; 43 | using Vector_t = std::vector; 44 | 45 | public: 46 | 47 | /// @brief Iterator for the vector. 48 | class Iterator { 49 | public: 50 | Iterator(Vector& data, size_t index) : m_data{data}, m_index{index} {} 51 | Iterator& operator++() { ++m_index; return *this; } 52 | bool operator!=(const Iterator& other) const { return m_index != other.m_index; } 53 | T& operator*() { return m_data[m_index]; } 54 | 55 | private: 56 | Vector& m_data; 57 | size_t m_index{0}; 58 | }; 59 | 60 | /// @brief Constructor, creates the vector. 61 | /// @param segmentBits The number of bits for the segment size. 62 | Vector(size_t segmentBits = 6) : m_size{0}, m_segmentBits(segmentBits), m_segmentSize{1ull< 0); 64 | m_segments.emplace_back( std::make_shared>(m_segmentSize) ); 65 | } 66 | 67 | ~Vector() = default; 68 | 69 | Vector( const Vector& other) : m_size{other.m_size}, m_segmentBits(other.m_segmentBits), m_segmentSize{other.m_segmentSize}, m_segments{} { 70 | m_segments.emplace_back( std::make_shared>(m_segmentSize) ); 71 | } 72 | 73 | /// @brief Push a value to the back of the vector. 74 | /// @param value The value to push. 75 | template 76 | auto push_back(U&& value) -> size_t { 77 | while( Segment(m_size) >= m_segments.size() ) { 78 | m_segments.emplace_back( std::make_shared>(m_segmentSize) ); 79 | } 80 | ++m_size; 81 | (*this)[m_size - 1] = std::forward(value); 82 | return m_size - 1; 83 | } 84 | 85 | auto push_back() -> size_t override { 86 | return push_back(T{}); 87 | } 88 | 89 | /// @brief Pop the last value from the vector. 90 | void pop_back() override { 91 | assert(m_size > 0); 92 | --m_size; 93 | if( Offset(m_size) == 0 && m_segments.size() > 1 ) { 94 | m_segments.pop_back(); 95 | } 96 | } 97 | 98 | /// @brief Get the value at an index. 99 | /// @param index The index of the value. 100 | auto operator[](size_t index) const -> T& { 101 | assert(index < m_size); 102 | auto seg = Segment(index); 103 | auto off = Offset(index); 104 | return (*m_segments[Segment(index)])[Offset(index)]; 105 | } 106 | 107 | /// @brief Get the value at an index. 108 | auto size() const -> size_t override { return m_size; } 109 | 110 | /// @brief Clear the vector. Make sure that one segment is always available. 111 | void clear() override { 112 | m_size = 0; 113 | m_segments.clear(); 114 | m_segments.emplace_back( std::make_shared>(m_segmentSize) ); 115 | } 116 | 117 | /// @brief Erase an entity from the vector. 118 | auto erase(size_t index) -> size_t override { 119 | size_t last = size() - 1; 120 | assert(index <= last); 121 | if( index < last ) { 122 | (*this)[index] = std::move( (*this)[last] ); //move the last entity to the erased one 123 | } 124 | pop_back(); //erase the last entity 125 | return last; //if index < last then last element was moved -> correct mapping 126 | } 127 | 128 | /// @brief Copy an entity from another vector to this. 129 | void copy(VectorBase* other, size_t from) override { 130 | push_back( (static_cast*>(other))->operator[](from) ); 131 | } 132 | 133 | /// @brief Swap two entities in the vector. 134 | void swap(size_t index1, size_t index2) override { 135 | std::swap( (*this)[index1], (*this)[index2] ); 136 | } 137 | 138 | /// @brief Clone the vector. 139 | auto clone() -> std::unique_ptr override { 140 | return std::make_unique>(); 141 | } 142 | 143 | /// @brief Print the vector. 144 | void print() override { 145 | std::cout << "Name: " << typeid(T).name() << " ID: " << Type(); 146 | } 147 | 148 | auto begin() -> Iterator { return Iterator{*this, 0}; } 149 | auto end() -> Iterator { return Iterator{*this, m_size}; } 150 | 151 | private: 152 | 153 | /// @brief Compute the segment index of an entity index. 154 | /// @param index Entity index. 155 | /// @return Index of the segment. 156 | inline size_t Segment(size_t index) const { return index >> m_segmentBits; } 157 | 158 | /// @brief Compute the offset of an entity index in a segment. 159 | /// @param index Entity index. 160 | /// @return Offset in the segment. 161 | inline size_t Offset(size_t index) const { return index & (m_segmentSize-1ul); } 162 | 163 | size_t m_size{0}; ///< Size of the vector. 164 | size_t m_segmentBits; ///< Number of bits for the segment size. 165 | size_t m_segmentSize; ///< Size of a segment. 166 | Vector_t m_segments{10}; ///< Vector holding unique pointers to the segments. 167 | }; //end of Vector 168 | 169 | } 170 | -------------------------------------------------------------------------------- /include/old/VECS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include // std::sort 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | #include "VECSVector.h" 28 | #include "VECSHashMap.h" 29 | #include "VECSArchetype.h" 30 | #include "VECSArchetype2.h" 31 | #include "VECSSlotMap.h" 32 | #include "VECSMutex.h" 33 | #include "VECSHandle.h" 34 | 35 | using namespace std::chrono_literals; 36 | 37 | 38 | 39 | namespace vecs { 40 | 41 | 42 | //---------------------------------------------------------------------------------------------- 43 | //Registry concepts and types 44 | 45 | template 46 | concept VecsView = ((vtll::unique>::value) && (sizeof...(Ts) > 0) && (!std::is_same_v && ...)); 47 | 48 | template 49 | concept VecsIterator = (vtll::unique>::value); 50 | 51 | template requires VecsIterator class Iterator; 52 | template requires VecsView class View; 53 | 54 | 55 | 56 | //---------------------------------------------------------------------------------------------- 57 | //Registry 58 | 59 | const int REGISTRYTYPE_SEQUENTIAL = 0; 60 | const int REGISTRYTYPE_PARALLEL = 1; 61 | 62 | template 63 | concept RegistryType = (T == REGISTRYTYPE_SEQUENTIAL || T == REGISTRYTYPE_PARALLEL); 64 | 65 | /// @brief A registry for entities and components. 66 | template 67 | requires RegistryType 68 | class Registry { 69 | 70 | /// @brief Entry for the seach cache 71 | struct TypeSetAndHash { 72 | std::set m_types; //set of types that have been searched for 73 | size_t m_hash; //hash of the set 74 | }; 75 | 76 | template 77 | struct SlotMapAndMutex { 78 | SlotMap m_slotMap; 79 | mutex_t m_mutex; 80 | SlotMapAndMutex( uint32_t storageIndex, uint32_t bits ) : m_slotMap{storageIndex, bits}, m_mutex{} {}; 81 | SlotMapAndMutex( const SlotMapAndMutex& other ) : m_slotMap{other.m_slotMap}, m_mutex{} {}; 82 | }; 83 | 84 | using NUMBER_SLOTMAPS = std::conditional_t< RTYPE == REGISTRYTYPE_SEQUENTIAL, 85 | std::integral_constant, std::integral_constant>; 86 | 87 | 88 | public: 89 | 90 | //---------------------------------------------------------------------------------------------- 91 | 92 | /// @brief A structure holding a pointer to an archetype and the current size of the archetype. 93 | struct ArchetypeAndSize { 94 | Archetype* m_archetype; //pointer to the archetype 95 | size_t m_size; //size of the archetype 96 | ArchetypeAndSize(Archetype* arch, size_t size) : m_archetype{arch}, m_size{size} {} 97 | }; 98 | 99 | /// @brief Used for iterating over entity components. Iterators are created by a view. 100 | /// When iterating over an archetype, the archetype is locked in shared mode to prevent changes. 101 | /// The iterator unlocks the archetype when it changes to another archetype or when it is destroyed. 102 | /// Because of the shared lock, the iterator can be used in parallel for different archetypes, but 103 | /// there are opertions that must be delayed until the shared lock is freed. 104 | /// @tparam ...Ts Choose the types of the components you want the entities to have. 105 | template 106 | requires VecsIterator 107 | class Iterator { 108 | 109 | public: 110 | /// @brief Iterator constructor saving a list of archetypes and the current index. 111 | /// @param arch List of archetypes. 112 | /// @param archidx First archetype index. 113 | Iterator( Registry& system, std::vector& arch, size_t archidx) 114 | : m_system(system), m_archetypes{arch}, m_archidx{archidx} { 115 | 116 | system.increaseIterators(); //Tell the system that an iterator is created 117 | if( m_archetypes.size() > 0 ) { 118 | m_size = std::min(m_archetypes.front().m_size, m_archetypes.front().m_archetype->Size()); 119 | } 120 | Next(); //go to the first entity 121 | } 122 | 123 | /// @brief Copy constructor. 124 | Iterator(const Iterator& other) : m_system{other.m_system}, m_archetypes{other.m_archetypes}, 125 | m_archidx{other.m_archidx}, m_size{other.m_size}, m_entidx{other.m_entidx}, m_isLocked{false} { 126 | m_system.increaseIterators(); 127 | } 128 | 129 | /// @brief Destructor, unlocks the archetype. 130 | ~Iterator() { 131 | UnlockShared(); 132 | if( m_system.decreaseIterators() == 0 ) m_system.RunDelayedTransactions(); 133 | } 134 | 135 | /// @brief Prefix increment operator. 136 | auto operator++() { 137 | ++m_entidx; 138 | Next(); 139 | return *this; 140 | } 141 | 142 | /// @brief Access the content the iterator points to. 143 | auto operator*() { 144 | assert( m_archetype && m_entidx < m_size ); 145 | Handle handle = (*m_mapHandle)[m_entidx]; 146 | auto tup = std::make_tuple( Get(m_system, handle, m_archetype)... ); 147 | if constexpr (sizeof...(Ts) == 1) { return std::get<0>(tup); } 148 | else return tup; 149 | } 150 | 151 | /// @brief Compare two iterators. 152 | auto operator!=(const Iterator& other) -> bool { 153 | return (m_archidx != other.m_archidx) || (m_entidx != other.m_entidx); 154 | } 155 | 156 | private: 157 | 158 | /// @brief Get a component value from the archetype. 159 | template 160 | requires (!std::is_same_v) // && !std::is_reference_v) 161 | [[nodiscard]] auto Get(auto &system, Handle handle, Archetype* arch) { 162 | using T = std::decay_t; 163 | return (*arch->template Map())[m_entidx]; 164 | } 165 | 166 | /// @brief Go to the next entity. If this means changing the archetype, unlock the current archetype and lock the new one. 167 | void Next() { 168 | while( m_archidx < m_archetypes.size() && m_entidx >= std::min(m_archetypes[m_archidx].m_size, m_archetypes[m_archidx].m_archetype->Size()) ) { 169 | if( m_isLocked ) UnlockShared(); 170 | ++m_archidx; 171 | m_entidx = 0; 172 | } 173 | if( m_archidx < m_archetypes.size() ) { 174 | m_archetype = m_archetypes[m_archidx].m_archetype; 175 | m_size = std::min(m_archetypes[m_archidx].m_size, m_archetypes[m_archidx].m_archetype->Size()); 176 | m_mapHandle = m_archetype->template Map(); 177 | LockShared(); 178 | } else { 179 | m_archetype = nullptr; 180 | } 181 | } 182 | 183 | /// @brief Lock the archetype in shared mode. 184 | void LockShared() { 185 | if constexpr (RTYPE == REGISTRYTYPE_PARALLEL) { 186 | if( !m_archetype || m_isLocked ) return; 187 | m_archetype->GetMutex().lock_shared(); 188 | m_system.m_currentArchetype = m_archetype; 189 | m_isLocked = true; 190 | } 191 | } 192 | 193 | /// @brief Unlock the archetype. 194 | void UnlockShared() { 195 | if constexpr (RTYPE == REGISTRYTYPE_PARALLEL) { 196 | if( !m_archetype || !m_isLocked ) return; 197 | m_archetype->GetMutex().unlock_shared(); 198 | m_system.m_currentArchetype = nullptr; 199 | m_isLocked = false; 200 | } 201 | } 202 | 203 | Registry& m_system; ///< Reference to the registry system. 204 | Archetype* m_archetype{nullptr}; ///< Pointer to the current archetype. 205 | Vector* m_mapHandle{nullptr}; ///< Pointer to the comp map holding the handle of the current archetype. 206 | std::vector& m_archetypes; ///< List of archetypes. 207 | size_t m_archidx{0}; ///< Index of the current archetype. 208 | size_t m_size{0}; ///< Size of the current archetype. 209 | size_t m_entidx{0}; ///< Index of the current entity. 210 | bool m_isLocked{false}; ///< Flag if the archetype is locked. 211 | }; //end of Iterator 212 | 213 | 214 | //---------------------------------------------------------------------------------------------- 215 | 216 | 217 | /// @brief A view of entities with specific components. 218 | /// @tparam ...Ts The types of the components. 219 | template 220 | requires VecsView 221 | class View { 222 | 223 | public: 224 | template 225 | View(Registry& system, U&& tagsYes, U&& tagsNo ) : 226 | m_system{system}, m_tagsYes{std::forward(tagsYes)}, m_tagsNo{std::forward(tagsNo)} { 227 | } ///< Constructor. 228 | 229 | /// @brief Get an iterator to the first entity. 230 | /// The archetype is locked in shared mode to prevent changes. 231 | /// @return Iterator to the first entity. 232 | auto begin() { 233 | m_archetypes.clear(); 234 | auto types = std::vector({Type>()...}); 235 | auto hs = Hash(types); 236 | { 237 | LockGuardShared lock(&m_system.GetMutex()); //lock the cache 238 | if(FindAndCopy(hs) ) return Iterator{m_system, m_archetypes, 0}; 239 | } 240 | 241 | LockGuard lock(&m_system.GetMutex()); //lock the cache 242 | if(FindAndCopy(hs) ) return Iterator{m_system, m_archetypes, 0}; 243 | 244 | auto& archetypes = m_system.m_searchCacheMap[hs]; //create empty set 245 | assert(archetypes.size() == 0); 246 | for( auto& it : m_system.m_archetypes ) { 247 | auto arch = it.second.get(); 248 | auto func = [&]() { 249 | if( arch->Types().contains(Type>())) return true; 250 | return false; 251 | }; 252 | if( (func.template operator()() && ...) ) archetypes.insert(arch); 253 | } 254 | m_system.m_searchCacheSet.emplace_back(TypeSetAndHash{{}, hs}); 255 | ( m_system.m_searchCacheSet.back().m_types.insert(Type>()), ... ); 256 | FindAndCopy(hs); 257 | return Iterator{m_system, m_archetypes, 0}; 258 | } 259 | 260 | /// @brief Get an iterator to the end of the view. 261 | auto end() { 262 | return Iterator{m_system, m_archetypes, m_archetypes.size()}; 263 | } 264 | 265 | private: 266 | 267 | /// @brief Find archetypes with the same components in the search cache and copy them to the list. 268 | /// @param hs Hash of the types of the components. 269 | /// @return true if the archetypes were found in the cache, else false. 270 | inline auto FindAndCopy2(size_t hs) -> bool { 271 | if( m_system.m_searchCacheMap.contains(hs) ) { 272 | for( auto& arch : m_system.m_searchCacheMap[hs] ) { 273 | m_archetypes.emplace_back( arch, arch->Size() ); 274 | } 275 | return true; 276 | } 277 | return false; 278 | } 279 | 280 | inline auto FindAndCopy(size_t hs) -> bool { 281 | for( auto& arch : m_system.m_archetypes ) { 282 | bool found = (arch.second->Types().contains(Type>()) && ...) && 283 | (std::ranges::all_of( m_tagsYes, [&](size_t ti){ return arch.second->Types().contains(ti); } )) && 284 | (std::ranges::none_of( m_tagsNo, [&](size_t ti){ return arch.second->Types().contains(ti); } ) ); 285 | if(found) m_archetypes.emplace_back( arch.second.get(), arch.second->Size() ); 286 | } 287 | return true; 288 | } 289 | 290 | Registry& m_system; ///< Reference to the registry system. 291 | std::vector m_tagsYes; ///< List of tags that must be present. 292 | std::vector m_tagsNo; ///< List of tags that must not be present. 293 | std::vector m_archetypes; ///< List of archetypes. 294 | }; //end of View 295 | 296 | //---------------------------------------------------------------------------------------------- 297 | 298 | 299 | template< template typename Y, typename... Ts, template typename N, typename... Us > 300 | requires (VecsView) 301 | class View, N> { 302 | 303 | public: 304 | template 305 | View(Registry& system, U&& tagsYes, U&& tagsNo ) : 306 | m_system{system}, m_tagsYes{std::forward(tagsYes)}, m_tagsNo{std::forward(tagsNo)} {} ///< Constructor. 307 | 308 | /// @brief Get an iterator to the first entity. 309 | /// The archetype is locked in shared mode to prevent changes. 310 | /// @return Iterator to the first entity. 311 | auto begin() { 312 | m_archetypes.clear(); 313 | auto types = std::vector({ Type...>>(), Type...>>() } ); 314 | auto hs = Hash(types); 315 | { 316 | LockGuardShared lock(&m_system.GetMutex()); //lock the cache 317 | if(FindAndCopy(hs) ) return Iterator{m_system, m_archetypes, 0}; 318 | } 319 | 320 | LockGuard lock(&m_system.GetMutex()); //lock the cache 321 | if(FindAndCopy(hs) ) return Iterator{m_system, m_archetypes, 0}; 322 | 323 | auto& archetypes = m_system.m_searchCacheMap[hs]; //create empty set 324 | assert(archetypes.size() == 0); 325 | 326 | for( auto& it : m_system.m_archetypes ) { 327 | 328 | auto arch = it.second.get(); 329 | bool found = false; 330 | auto func = [&]() { 331 | if( arch->Types().contains(Type>())) return true; 332 | return false; 333 | }; 334 | 335 | found = (func.template operator()() && ...) && (!func.template operator()() && ...); 336 | if(found) archetypes.insert(arch); 337 | } 338 | m_system.m_searchCacheSet.emplace_back(TypeSetAndHash{{}, hs}); 339 | ( m_system.m_searchCacheSet.back().m_types.insert(Type>()), ... ); 340 | FindAndCopy(hs); 341 | return Iterator{m_system, m_archetypes, 0}; 342 | } 343 | 344 | /// @brief Get an iterator to the end of the view. 345 | auto end() { 346 | return Iterator{m_system, m_archetypes, m_archetypes.size()}; 347 | } 348 | 349 | private: 350 | 351 | /// @brief Find archetypes with the same components in the search cache and copy them to the list. 352 | /// @param hs Hash of the types of the components. 353 | /// @return true if the archetypes were found in the cache, else false. 354 | inline auto FindAndCopy2(size_t hs) -> bool { 355 | if( m_system.m_searchCacheMap.contains(hs) ) { 356 | for( auto& arch : m_system.m_searchCacheMap[hs] ) { 357 | m_archetypes.emplace_back( arch, arch->Size() ); 358 | } 359 | return true; 360 | } 361 | return false; 362 | } 363 | 364 | inline auto FindAndCopy(size_t hs) -> bool { 365 | for( auto& arch : m_system.m_archetypes ) { 366 | bool found = (arch.second->Types().contains(Type>()) && ...) && 367 | (!arch.second->Types().contains(Type>()) && ...) && 368 | (std::ranges::all_of( m_tagsYes, [&](size_t ti){ return arch.second->Types().contains(ti); } )) && 369 | (std::ranges::none_of( m_tagsNo, [&](size_t ti){ return arch.second->Types().contains(ti); } ) ); 370 | 371 | if(found) m_archetypes.emplace_back( arch.second.get(), arch.second->Size() ); 372 | } 373 | return true; 374 | } 375 | 376 | Registry& m_system; ///< Reference to the registry system. 377 | std::vector m_tagsYes; ///< List of tags that must be present. 378 | std::vector m_tagsNo; ///< List of tags that must not be present. 379 | std::vector m_archetypes; ///< List of archetypes. 380 | 381 | }; 382 | 383 | 384 | //---------------------------------------------------------------------------------------------- 385 | 386 | Registry() { ///< Constructor. 387 | //m_entities.reserve(NUMBER_SLOTMAPS::value); //resize the slot storage 388 | for( uint32_t i = 0; i < NUMBER_SLOTMAPS::value; ++i ) { 389 | m_entities.emplace_back( SlotMapAndMutex::ArchetypeAndIndex>{ i, 6 } ); //add the first slot 390 | } 391 | }; 392 | 393 | ~Registry() = default; ///< Destructor. 394 | 395 | /// @brief Get the number of entities in the system. 396 | /// @return The number of entities. 397 | size_t Size() { 398 | size_t size = 0; 399 | for( auto& slot : m_entities ) { 400 | size += slot.m_slotMap.Size(); 401 | } 402 | return size; 403 | } 404 | 405 | /// @brief Create an entity with components. 406 | /// @tparam ...Ts The types of the components. 407 | /// @param ...component The new values. 408 | /// @return Handle of new entity. 409 | template 410 | requires ((sizeof...(Ts) > 0) && (vtll::unique>::value) && !vtll::has_type< vtll::tl, Handle>::value) 411 | [[nodiscard]] auto Insert( Ts&&... component ) -> Handle { 412 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 413 | 414 | size_t slotMapIndex = GetSlotmapIndex(); 415 | LockGuard lock(&GetMutex(slotMapIndex)); //lock the mutex 416 | auto [handle, slot] = m_entities[slotMapIndex].m_slotMap.Insert( {nullptr, 0} ); //get a slot for the entity 417 | 418 | size_t archIndex; 419 | std::vector types = {Type(), Type()...}; 420 | size_t hs = Hash(types); 421 | if( !m_archetypes.contains( hs ) ) { //not found 422 | auto arch = std::make_unique>( handle, archIndex, std::forward(component)... ); 423 | UpdateSearchCache(arch.get()); 424 | slot.m_value = { arch.get(), archIndex }; 425 | m_archetypes[hs] = std::move(arch); 426 | } else { 427 | auto arch = m_archetypes[hs].get(); 428 | LockGuard lock(&arch->GetMutex()); //lock the mutex 429 | slot.m_value = { arch, arch->Insert( handle, std::forward(component)... ) }; 430 | } 431 | return handle; 432 | } 433 | 434 | /// @brief Test if an entity exists. 435 | /// @param handle The handle of the entity. 436 | /// @return true if the entity exists, else false. 437 | bool Exists(Handle handle) { 438 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 439 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 440 | auto& slot = m_entities[handle.GetStorageIndex()].m_slotMap[handle]; 441 | return slot.m_version == handle.GetVersion(); 442 | } 443 | 444 | /// @brief Test if an entity has a component. 445 | /// @tparam T The type of the component. 446 | /// @param handle The handle of the entity. 447 | /// @return true if the entity has the component, else false. 448 | template 449 | bool Has(Handle handle) { 450 | assert(Exists(handle)); 451 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 452 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the system 453 | auto arch = m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value.m_archetypePtr; 454 | return arch->Has(Type()); 455 | } 456 | 457 | /// @brief Test if an entity has a tag. 458 | /// @param handle The handle of the entity. 459 | /// @param tag The tag to test. 460 | /// @return true if the entity has the tag, else false. 461 | bool Has(Handle handle, size_t ti) { 462 | assert(Exists(handle)); 463 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 464 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the system 465 | auto arch = m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value.m_archetypePtr; 466 | return arch->Has(ti); 467 | } 468 | 469 | /// @brief Get the types of the components of an entity. 470 | /// @param handle The handle of the entity. 471 | /// @return A vector of type indices of the components. 472 | const auto& Types(Handle handle) { 473 | assert(Exists(handle)); 474 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 475 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the system 476 | auto arch = m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value.m_archetypePtr; 477 | return arch->Types(); 478 | } 479 | 480 | /// @brief Get a component value of an entity. 481 | /// @tparam T The type of the component. 482 | /// @param handle The handle of the entity. 483 | /// @return The component value or reference to it. 484 | template 485 | [[nodiscard]] auto Get(Handle handle) -> T { 486 | assert(Exists(handle)); 487 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 488 | return Get2(handle); 489 | } 490 | 491 | /// @brief Get component values of an entity. 492 | /// @tparam Ts The types of the components. 493 | /// @param handle The handle of the entity. 494 | /// @return A tuple of the component values. 495 | template 496 | requires (sizeof...(Ts)>1 && vtll::unique>::value && !vtll::has_type< vtll::tl, Handle&>::value) 497 | [[nodiscard]] auto Get(Handle handle) -> std::tuple { 498 | assert(Exists(handle)); 499 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 500 | return Get2(handle); 501 | } 502 | 503 | /// @brief Put a new component value to an entity. If the entity does not have the component, it will be created. 504 | /// This might result in moving the entity to another archetype. 505 | /// @tparam T The type of the component. 506 | /// @param handle The handle of the entity. 507 | /// @param v The new value. 508 | template 509 | requires (!is_tuple::value && !std::is_same_v, Handle>) 510 | void Put(Handle handle, U&& v) { 511 | assert(Exists(handle)); 512 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 513 | using T = std::decay_t; 514 | Put2(handle, std::forward(v)); 515 | } 516 | 517 | /// @brief Put new component values to an entity. 518 | /// @tparam Ts The types of the components. 519 | /// @param handle The handle of the entity. 520 | /// @param v The new values in a tuple 521 | template 522 | requires (vtll::unique>::value && !vtll::has_type< vtll::tl...>, Handle>::value) 523 | void Put(Handle handle, std::tuple& v) { 524 | assert(Exists(handle)); 525 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 526 | Put2(handle, std::forward(std::get(v))...); 527 | } 528 | 529 | /// @brief Put new component values to an entity. 530 | /// @tparam Ts The types of the components. 531 | /// @param handle The handle of the entity. 532 | /// @param ...vs The new values. 533 | template 534 | requires ((sizeof...(Ts) > 1) && (vtll::unique>::value) && !vtll::has_type< vtll::tl...>, Handle>::value) 535 | void Put(Handle handle, Ts&&... vs) { 536 | assert(Exists(handle)); 537 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 538 | Put2(handle, std::forward(vs)...); 539 | } 540 | 541 | /// @brief Add tags to an entity. 542 | template 543 | requires (sizeof...(Ts) > 0 && (std::is_convertible_v && ...) ) 544 | void AddTags(Handle handle, Ts... tags) { 545 | assert(Exists(handle)); 546 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 547 | AddTags2(handle, tags...); 548 | } 549 | 550 | /// @brief Add tags to an entity. 551 | template 552 | requires (sizeof...(Ts) > 0 && (std::is_convertible_v && ...) ) 553 | void EraseTags(Handle handle, Ts... tags) { 554 | assert(Exists(handle)); 555 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 556 | EraseTags2(handle, tags...); 557 | } 558 | 559 | /// @brief Erase components from an entity. 560 | /// @tparam ...Ts The types of the components. 561 | /// @param handle The handle of the entity. 562 | template 563 | requires (vtll::unique>::value && !vtll::has_type< vtll::tl, Handle>::value) 564 | void Erase(Handle handle) { 565 | assert( (Has(handle) && ...) ); 566 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 567 | 568 | LockGuard lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 569 | auto value = &m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value; 570 | auto oldArch = value->m_archetypePtr; 571 | std::set types = oldArch->Types(); 572 | (types.erase(Type()), ... ); 573 | size_t hs = Hash(types); 574 | Archetype *arch; 575 | if( !m_archetypes.contains(hs) ) { 576 | auto archPtr = std::make_unique>(); 577 | arch = archPtr.get(); 578 | arch->Clone(*oldArch, types); 579 | UpdateSearchCache(arch); 580 | m_archetypes[hs] = std::move(archPtr); 581 | } else { arch = m_archetypes[hs].get(); } 582 | LockGuard lock2(&arch->GetMutex(), &oldArch->GetMutex()); 583 | value->m_archetypePtr = arch; 584 | value->m_archIndex = arch->Move(types, value->m_archIndex, *oldArch, m_entities); 585 | } 586 | 587 | /// @brief Erase an entity from the registry. 588 | /// @param handle The handle of the entity. 589 | void Erase(Handle handle) { 590 | assert(Exists(handle)); 591 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 592 | LockGuard lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 593 | auto& value = m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value; 594 | LockGuard lock2(&value.m_archetypePtr->GetMutex()); //lock the archetype 595 | value.m_archetypePtr->Erase(value.m_archIndex, m_entities); //erase the entity from the archetype (do both locked) 596 | m_entities[handle.GetStorageIndex()].m_slotMap.Erase(handle); //erase the entity from the entity list 597 | } 598 | 599 | /// @brief Clear the registry by removing all entities. 600 | void Clear() { 601 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 602 | 603 | for( auto& arch : m_archetypes ) { 604 | LockGuard lock(&arch.second->GetMutex()); //lock the mutex 605 | arch.second->Clear(); 606 | } 607 | for( auto& slotmap : m_entities ) { 608 | LockGuard lock(&slotmap.m_mutex); //lock the mutex 609 | slotmap.m_slotMap.Clear(); 610 | } 611 | LockGuard lock(&GetMutex()); //lock the mutex 612 | m_searchCacheMap.clear(); 613 | m_searchCacheSet.clear(); 614 | } 615 | 616 | /// @brief Get a view of entities with specific components. 617 | /// @tparam ...Ts The types of the components. 618 | /// @return A view of the entity components 619 | template 620 | requires (vtll::unique>::value) 621 | [[nodiscard]] auto GetView() -> View { 622 | return {*this, std::vector{}, std::vector{}}; 623 | } 624 | 625 | template 626 | requires (vtll::unique>::value) 627 | [[nodiscard]] auto GetView(std::vector&& yes) -> View { 628 | return {*this, std::forward>(yes), std::vector{}}; 629 | } 630 | 631 | template 632 | requires (vtll::unique>::value) 633 | [[nodiscard]] auto GetView(std::vector&& yes, std::vector&& no) -> View { 634 | return {*this, std::forward>(yes), std::forward>(no),}; 635 | } 636 | 637 | /// @brief Print the registry. 638 | /// Print the number of entities and the archetypes. 639 | void Print() { 640 | std::cout << "-----------------------------------------------------------------------------------------------" << std::endl; 641 | std::cout << "Entities: " << Size() << std::endl; 642 | for( auto& it : m_archetypes ) { 643 | std::cout << "Archetype Hash: " << it.first << std::endl; 644 | it.second->Print(); 645 | } 646 | std::cout << "Cache Map " << m_searchCacheMap.size() << " Set: " << m_searchCacheSet.size() << std::endl; 647 | for( auto it : m_searchCacheMap ) { 648 | std::cout << "Hash: " << it.first << " Archetypes: " << it.second.size() << std::endl; 649 | } 650 | std::cout << std::endl << std::endl; 651 | } 652 | 653 | /// @brief Validate the registry. 654 | /// Make sure all archetypes have the same size in all component maps. 655 | void Validate() { 656 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 657 | LockGuardShared lock(&GetMutex()); 658 | for( auto& it : m_archetypes ) { 659 | auto arch = it.second.get(); 660 | LockGuardShared lock(&arch->GetMutex()); 661 | arch->Validate(); 662 | } 663 | } 664 | 665 | /// @brief Increase the number of iterators. 666 | /// @return number of iterators. 667 | size_t increaseIterators() { 668 | return ++m_numIterators; 669 | } 670 | 671 | /// @brief Decrease the number of iterators. 672 | /// @return number of iterators. 673 | size_t decreaseIterators() { 674 | assert(m_numIterators > 0); 675 | return --m_numIterators; 676 | } 677 | 678 | inline bool CheckUnlockArchetype() { 679 | if constexpr( RTYPE == REGISTRYTYPE_PARALLEL ) { 680 | if( m_currentArchetype ) { 681 | m_currentArchetype->GetMutex().unlock_shared(); 682 | return true; 683 | } 684 | } 685 | return false; 686 | } 687 | 688 | inline bool CheckLockArchetype() { 689 | if constexpr( RTYPE == REGISTRYTYPE_PARALLEL ) { 690 | if( m_currentArchetype ) { 691 | m_currentArchetype->GetMutex().lock_shared(); 692 | return true; 693 | } 694 | } 695 | return false; 696 | } 697 | 698 | /// @brief Delay a transaction until all iterators are destroyed. 699 | bool DelayTransaction( auto&& func ) { 700 | if constexpr( RTYPE == REGISTRYTYPE_PARALLEL ) { 701 | if( m_numIterators > 0 ) { 702 | m_delayedTransactions.emplace_back(func); 703 | return true; 704 | } 705 | } 706 | func(); 707 | return false; 708 | } 709 | 710 | /// @brief Run all delayed transactions. Since each thread has its own list of delayed transactions, 711 | /// this function must be called by each thread, and does not need synchronization. 712 | void RunDelayedTransactions() { 713 | for( auto& func : m_delayedTransactions ) { 714 | func(); 715 | } 716 | m_delayedTransactions.clear(); 717 | } 718 | 719 | /// @brief Get the mutex of the archetype. 720 | /// @return Reference to the mutex. 721 | [[nodiscard]] inline auto GetMutex(size_t index) -> mutex_t& { 722 | return m_entities[index].m_mutex; 723 | } 724 | 725 | /// @brief Get the mutex of the archetype. 726 | /// @return Reference to the mutex. 727 | [[nodiscard]] inline auto GetMutex() -> mutex_t& { 728 | return m_mutex; 729 | } 730 | 731 | /// @brief Get the index of the entity in the archetype 732 | auto GetArchetypeIndex( Handle handle ) { 733 | assert(Exists(handle)); 734 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the system 735 | return m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value.m_archIndex; 736 | } 737 | 738 | /// @brief Swap two entities. 739 | /// @param h1 The handle of the first entity. 740 | /// @param h2 The handle of the second entity. 741 | /// @return true if the entities were swapped, else false. 742 | bool Swap( Handle h1, Handle h2 ) { 743 | assert(Exists(h1) && Exists(h2)); 744 | UnlockGuardShared unlock(m_currentArchetype); //unlock the current archetype 745 | size_t index1 = h1.GetStorageIndex(); 746 | size_t index2 = h2.GetStorageIndex(); 747 | mutex_t* m1 = &GetMutex(index1); 748 | mutex_t* m2 = &GetMutex(index2); 749 | if( index1 == index2 ) { m2 = nullptr; } //same slotmap -> only lock once 750 | LockGuard lock(m1, m2); //lock the slotmap(s) 751 | auto& slot1 = m_entities[index1].m_slotMap[h1]; 752 | auto& slot2 = m_entities[index2].m_slotMap[h2]; 753 | if( slot1.m_archetypePtr != slot2.m_archetypePtr ) return false; 754 | LockGuard lock2(&slot1.m_archetypePtr->GetMutex()); //lock the archetype 755 | slot1.m_archetypePtr->Swap(slot1, slot2); 756 | return true; 757 | } 758 | 759 | private: 760 | 761 | /// @brief Get the index of the slotmap to use and the archetype. 762 | /// @param handle The handle of the entity. 763 | /// @return The index of the slotmap and the archetype. 764 | auto GetSlotAndArch(auto handle) { 765 | typename Archetype::ArchetypeAndIndex* value = &m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value; 766 | return std::make_pair(value, value->m_archetypePtr); 767 | } 768 | 769 | /// @brief Clone an archetype with a subset of components. 770 | /// @param arch The archetype to clone. 771 | /// @param types The types of the components to clone. 772 | auto CloneArchetype(Archetype* arch, const std::vector& types) { 773 | auto newArch = std::make_unique>(); 774 | newArch->Clone(*arch, types); 775 | UpdateSearchCache(newArch.get()); 776 | m_archetypes[Hash(newArch->Types())] = std::move(newArch); 777 | } 778 | 779 | /// @brief Add tags to an entity. 780 | /// @tparam ...Ts The types of the tags must be size_t. 781 | /// @param handle The handle of the entity. 782 | /// @param ...tags The tags to add. 783 | template 784 | void AddTags2(Handle handle, Ts... tags) { 785 | LockGuard lock1(&GetMutex(handle.GetStorageIndex())); //lock the mutex 786 | auto [value, arch] = GetSlotAndArch(handle); 787 | assert(!arch->Types().contains(tags) && ...); 788 | std::vector allTypes{ tags...}; 789 | std::ranges::copy(arch->Types(), std::back_inserter(allTypes)); 790 | size_t hs = Hash(allTypes); 791 | if( !m_archetypes.contains(hs) ) { CloneArchetype(arch, allTypes); } 792 | auto newArch = m_archetypes[hs].get(); 793 | LockGuard lock2(&arch->GetMutex()); //lock the old archetype 794 | *value = { newArch, newArch->Move(arch->Types(), value->m_archIndex, *arch, m_entities) }; 795 | } 796 | 797 | /// @brief Erase tags from an entity. 798 | /// @tparam ...Ts The types of the tags must be size_t. 799 | /// @param handle The handle of the entity. 800 | /// @param ...tags The tags to erase. 801 | template 802 | void EraseTags2(Handle handle, Ts... tags) { 803 | LockGuard lock1(&GetMutex(handle.GetStorageIndex())); //lock the mutex 804 | auto [value, arch] = GetSlotAndArch(handle); 805 | std::vector delTypes{tags...}; 806 | std::vector allTypes; 807 | auto found = [&](size_t x) -> bool { return std::ranges::find_if( delTypes, [&](size_t y){ return x==y; } ) != delTypes.end(); }; 808 | for( auto x : arch->Types() ) { if( !found(x) ) allTypes.push_back(x); } 809 | size_t hs = Hash(allTypes); 810 | if( !m_archetypes.contains(hs) ) { CloneArchetype(arch, allTypes); } 811 | auto newArch = m_archetypes[hs].get(); 812 | LockGuard lock2(&arch->GetMutex(), &newArch->GetMutex()); //lock the old archetype 813 | *value = { newArch, newArch->Move(allTypes, value->m_archIndex, *arch, m_entities) }; 814 | } 815 | 816 | /// @brief Change the component values of an entity. 817 | /// @tparam ...Ts The types of the components. 818 | /// @param handle The handle of the entity. 819 | /// @param ...vs The new values. 820 | template 821 | void Put2(Handle handle, Ts&&... vs) { 822 | auto func = [&](Archetype* arch, size_t archIndex, T&& v) { 823 | (*arch->template Map())[archIndex] = std::forward(v); 824 | }; 825 | 826 | std::vector newTypes; 827 | { 828 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 829 | typename Archetype::ArchetypeAndIndex* value = GetArchetype...>(handle, newTypes); 830 | //value->m_archetypePtr->Print(); 831 | if(newTypes.empty()) { 832 | ( func.template operator()(value->m_archetypePtr, value->m_archIndex, std::forward(vs)), ... ); 833 | return; 834 | }; 835 | } 836 | 837 | LockGuard lock1(&GetMutex(handle.GetStorageIndex())); //lock the mutex 838 | typename Archetype::ArchetypeAndIndex* value = GetArchetype...>(handle, newTypes); 839 | auto arch = value->m_archetypePtr; 840 | if(newTypes.empty()) { 841 | ( func.template operator()(arch, value->m_archIndex, std::forward(vs)), ... ); 842 | return; 843 | } 844 | auto newArch = CreateArchetype...>(handle, value, newTypes); 845 | 846 | value->m_archetypePtr = newArch; 847 | LockGuard lock2(&arch->GetMutex()); //lock old archetype, new one cannot be seen yet 848 | value->m_archIndex = newArch->Move(arch->Types(), value->m_archIndex, *arch, m_entities); //move values 849 | ( func.template operator()(newArch, value->m_archIndex, std::forward(vs)), ... ); 850 | UpdateSearchCache(newArch); //update the search cache 851 | return; 852 | } 853 | 854 | /// @brief Get component values of an entity. 855 | /// @tparam T The type of the components. 856 | /// @param handle The handle of the entity. 857 | /// @return Component values. 858 | template 859 | [[nodiscard]] auto Get2(Handle handle) -> U& { 860 | using T = std::decay_t; 861 | std::vector newTypes; 862 | { 863 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 864 | typename Archetype::ArchetypeAndIndex* value = GetArchetype(handle, newTypes); 865 | auto arch = value->m_archetypePtr; 866 | if(newTypes.empty()) { 867 | decltype(auto) v = arch->template Get(value->m_archIndex); 868 | return v; 869 | } 870 | } 871 | 872 | LockGuard lock1(&GetMutex(handle.GetStorageIndex())); //lock the mutex 873 | typename Archetype::ArchetypeAndIndex* value = GetArchetype(handle, newTypes); 874 | auto arch = value->m_archetypePtr; 875 | if(newTypes.empty()) { 876 | decltype(auto) v = arch->template Get(value->m_archIndex); 877 | return v; 878 | } 879 | auto newArch = CreateArchetype(handle, value, newTypes); 880 | value->m_archetypePtr = newArch; 881 | LockGuard lock2(&arch->GetMutex()); //lock old archetype, new one cannot be seen yet 882 | value->m_archIndex = newArch->Move(arch->Types(), value->m_archIndex, *arch, m_entities); //move values 883 | UpdateSearchCache(newArch); //update the search cache 884 | decltype(auto) v = newArch->template Get(value->m_archIndex); 885 | return v; 886 | } 887 | 888 | /// @brief Get component values of an entity. 889 | /// @tparam Ts The types of the components. 890 | /// @param handle The handle of the entity. 891 | /// @return A tuple of the component values. 892 | template 893 | requires (sizeof...(Ts)>1 && vtll::unique>::value && !vtll::has_type< vtll::tl, Handle&>::value) 894 | [[nodiscard]] auto Get2(Handle handle) -> std::tuple { 895 | using T = vtll::Nth_type, 0>; 896 | 897 | std::vector newTypes; 898 | { 899 | LockGuardShared lock(&GetMutex(handle.GetStorageIndex())); //lock the mutex 900 | typename Archetype::ArchetypeAndIndex* value = GetArchetype...>(handle, newTypes); 901 | auto arch = value->m_archetypePtr; 902 | //value->m_archetypePtr->Print(); 903 | if(newTypes.empty()) { return std::tuple{ arch->template Get(value->m_archIndex)... }; } 904 | } 905 | 906 | LockGuard lock1(&GetMutex(handle.GetStorageIndex())); //lock the mutex 907 | typename Archetype::ArchetypeAndIndex* value = GetArchetype...>(handle, newTypes); 908 | auto arch = value->m_archetypePtr; 909 | if(newTypes.empty()) { return std::tuple{ arch->template Get(value->m_archIndex)... } ; } 910 | 911 | auto newArch = CreateArchetype...>(handle, value, newTypes); 912 | value->m_archetypePtr = newArch; 913 | LockGuard lock2(&arch->GetMutex()); //lock old archetype, new one cannot be seen yet 914 | value->m_archIndex = newArch->Move(arch->Types(), value->m_archIndex, *arch, m_entities); //move values 915 | return std::tuple{ newArch->template Get(value->m_archIndex)... }; 916 | } 917 | 918 | /// @brief Get the archetype/index of an entity and the types of the components that are currently not contained. 919 | /// @tparam ...Ts The types of the components. 920 | /// @param handle The handle of the entity. 921 | /// @param newTypes A vector to store the types of the components that are not contained and should be created. 922 | /// @return Slotmap value of the entity containing pointer to the archetype and index in the archetype. 923 | template 924 | inline auto GetArchetype(Handle handle, std::vector& newTypes) -> Archetype::ArchetypeAndIndex* { 925 | newTypes.clear(); 926 | newTypes.reserve(sizeof...(Ts)); 927 | typename Archetype::ArchetypeAndIndex* value = &m_entities[handle.GetStorageIndex()].m_slotMap[handle].m_value; 928 | 929 | auto func = [&](){ if(!value->m_archetypePtr->Has(Type())) newTypes.emplace_back(Type()); }; 930 | ( func.template operator()>(), ... ); 931 | return value; 932 | } 933 | 934 | /// @brief Create a new archetype with the types of the components that are not contained. 935 | /// @tparam ...Ts The types of the components. 936 | /// @param handle The handle of the entity. 937 | /// @param value Pointer to the archetype/index of the entity. 938 | /// @param newTypes A vector with the types of the components that are not contained and shhould be created. 939 | /// @return Pointer to the new archetype. 940 | template 941 | inline auto CreateArchetype(Handle handle, Archetype::ArchetypeAndIndex* value, std::vector& newTypes) -> Archetype* { 942 | auto arch = value->m_archetypePtr; 943 | std::vector allTypes; //{newTypes}; 944 | std::ranges::copy( newTypes, std::back_inserter(allTypes) ); 945 | std::ranges::copy( arch->Types(), std::back_inserter(allTypes) ); 946 | 947 | size_t hs = Hash(allTypes); 948 | Archetype *newArch=nullptr; 949 | if( !m_archetypes.contains(hs) ) { 950 | auto newArchUnique = std::make_unique>(); 951 | newArch = newArchUnique.get(); 952 | newArch->Clone(*arch, arch->Types()); 953 | 954 | auto func = [&](){ 955 | if(!arch->Has(Type())) { //need the types, type indices are not enough 956 | newArch->template AddComponent(); 957 | newArch->AddValue(T{}); 958 | } 959 | }; 960 | ( func.template operator()>(), ... ); 961 | UpdateSearchCache(newArch); 962 | 963 | m_archetypes[hs] = std::move(newArchUnique); 964 | } else { 965 | newArch = m_archetypes[hs].get(); 966 | for( auto ti : newTypes ) { newArch->AddEmptyValue(ti); } 967 | } 968 | 969 | return newArch; 970 | } 971 | 972 | /// @brief Update the search cache with a new archetype. m_searchCacheSet contains the types of 973 | /// the components that have been searched for. FInd those previous search results that include 974 | /// all components that are searched for. Add them to the set of archetypes that yielded a result 975 | /// for this particular search. 976 | /// @param arch Pointer to the new archetype. 977 | void UpdateSearchCache(Archetype* arch) { 978 | LockGuard lock(&arch->GetMutex()); //lock the cache 979 | auto types = arch->Types(); 980 | for( auto& ts : m_searchCacheSet ) { 981 | if( std::includes(types.begin(), types.end(), ts.m_types.begin(), ts.m_types.end()) ) { 982 | m_searchCacheMap[ts.m_hash].insert(arch); 983 | } 984 | } 985 | } 986 | 987 | /// @brief Get a new index of the slotmap for the current thread. 988 | /// @return New index of the slotmap. 989 | size_t GetSlotmapIndex() { 990 | m_slotMapIndex = (m_slotMapIndex + 1) & (NUMBER_SLOTMAPS::value - 1); 991 | return m_slotMapIndex; 992 | } 993 | 994 | using SlotMaps_t = std::vector::ArchetypeAndIndex>>; 995 | using HashMap_t = HashMap>>; //change to std::map 996 | using SearchCacheMap_t = std::unordered_map*>>; 997 | using SearchCacheSet_t = std::vector; 998 | 999 | inline static thread_local size_t m_slotMapIndex = NUMBER_SLOTMAPS::value - 1; 1000 | 1001 | SlotMaps_t m_entities; 1002 | HashMap_t m_archetypes; //Mapping vector of type set hash to archetype 1:1. Internally synchronized! 1003 | 1004 | mutex_t m_mutex; //mutex for reading and writing search cache. Not needed for HashMaps. 1005 | SearchCacheMap_t m_searchCacheMap; //Mapping vector of hash to archetype, 1:N 1006 | SearchCacheSet_t m_searchCacheSet; //These type combinations have been searched for, for updating 1007 | inline static thread_local size_t m_numIterators{0}; //thread local variable for locking 1008 | inline static thread_local Archetype* m_currentArchetype{nullptr}; //is there an iterator now 1009 | inline static thread_local std::vector> m_delayedTransactions; //thread local variable for locking 1010 | }; 1011 | 1012 | } //end of namespace vecs 1013 | 1014 | 1015 | 1016 | -------------------------------------------------------------------------------- /include/old/VECSArchetype.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | 7 | namespace vecs { 8 | 9 | 10 | //---------------------------------------------------------------------------------------------- 11 | //Convenience functions 12 | 13 | template 14 | struct is_std_vector : std::false_type {}; 15 | 16 | template 17 | struct is_std_vector> : std::true_type {}; 18 | 19 | template struct is_tuple : std::false_type {}; 20 | template struct is_tuple> : std::true_type {}; 21 | 22 | template 23 | struct Yes {}; 24 | 25 | template 26 | struct No {}; 27 | 28 | /// @brief Compute the hash of a list of hashes. If stored in a vector, make sure that hashes are sorted. 29 | /// @tparam T Container type of the hashes. 30 | /// @param hashes Reference to the container of the hashes. 31 | /// @return Overall hash made from the hashes. 32 | template 33 | inline size_t Hash( T& hashes ) { 34 | std::size_t seed = 0; 35 | if constexpr ( is_std_vector>::value ) { 36 | std::ranges::sort(hashes); 37 | } 38 | for( auto& v : hashes ) { 39 | seed ^= v + 0x9e3779b9 + (seed<<6) + (seed>>2); 40 | } 41 | return seed; 42 | } 43 | 44 | template 45 | inline size_t Hash( T&& hashes ) { 46 | std::size_t seed = 0; 47 | if constexpr ( is_std_vector>::value ) { 48 | std::ranges::sort(hashes); 49 | } 50 | for( auto& v : hashes ) { 51 | seed ^= v + 0x9e3779b9 + (seed<<6) + (seed>>2); 52 | } 53 | return seed; 54 | } 55 | 56 | template 57 | concept VecsArchetype = (vtll::unique>::value && (sizeof...(Ts) > 0) && (!std::is_same_v> && ...)); 58 | 59 | const int ARCHETYPE_SEQUENTIAL = 0; 60 | const int ARCHETYPE_PARALLEL = 1; 61 | 62 | //---------------------------------------------------------------------------------------------- 63 | //Archetype 64 | 65 | /// @brief An archetype of entities with the same components. 66 | /// All entities that have the same components are stored in the same archetype. 67 | /// The components are stored in the component maps. Note that the archetype class is not templated, 68 | /// but some methods including a constructor are templated. Thus the class knows only type indices 69 | /// of its components, not the types themselves. 70 | template 71 | class Archetype; 72 | 73 | 74 | 75 | /// @brief An archetype of entities with the same components. 76 | /// All entities that have the same components are stored in the same archetype. 77 | template 78 | class Archetype { 79 | //template requires RegistryType friend class Registry; 80 | 81 | public: 82 | 83 | /// @brief A pair of an archetype and an index. This is stored in the slot map. 84 | struct ArchetypeAndIndex { 85 | Archetype* m_archetypePtr; //pointer to the archetype 86 | size_t m_archIndex; //index of the entity in the archetype 87 | }; 88 | 89 | Archetype() = default; //default constructor 90 | /// @brief Constructor, called if a new entity should be created with components, and the archetype does not exist yet. 91 | /// @tparam ...Ts 92 | /// @param handle The handle of the entity. 93 | /// @param ...values The values of the components. 94 | template 95 | requires VecsArchetype 96 | Archetype(Handle handle, size_t& archIndex, Ts&& ...values ) { 97 | (AddComponent(), ...); //insert component types 98 | (AddValue( std::forward(values) ), ...); //insert all components, get index of the handle 99 | AddComponent(); //insert the handle 100 | archIndex = AddValue( handle ); //insert the handle 101 | } 102 | 103 | /// @brief Insert a new entity with components to the archetype. 104 | /// @tparam ...Ts Value types of the components. 105 | /// @param handle The handle of the entity. 106 | /// @param ...values The values of the components. 107 | /// @return The index of the entity in the archetype. 108 | template 109 | requires VecsArchetype 110 | size_t Insert(Handle handle, Ts&& ...values ) { 111 | assert( m_maps.size() == sizeof...(Ts) + 1 ); 112 | assert( (m_maps.contains(Type()) && ...) ); 113 | (AddValue( std::forward(values) ), ...); //insert all components, get index of the handle 114 | return AddValue( handle ); //insert the handle 115 | } 116 | 117 | /// @brief Get referece to the types of the components. 118 | /// @return A reference to the container of the types. 119 | [[nodiscard]] const auto& Types() const { 120 | return m_types; 121 | } 122 | 123 | /// @brief Test if the archetype has a component. 124 | /// @param ti Hash of the type index of the component. 125 | /// @return true if the archetype has the component, else false. 126 | bool Has(const size_t ti) { 127 | return m_types.contains(ti); 128 | } 129 | 130 | /// @brief Get component value of an entity. 131 | /// @tparam T The type of the component. 132 | /// @param archIndex The index of the entity in the archetype. 133 | /// @return The component value. 134 | template 135 | [[nodiscard]] auto Get(size_t archIndex) -> T& { 136 | return (*Map())[archIndex]; 137 | } 138 | 139 | /// @brief Get component values of an entity. 140 | /// @tparam ...Ts Types of the components to get. 141 | /// @param handle Handle of the entity. 142 | /// @return A tuple of the component values. 143 | template 144 | requires ((sizeof...(Ts) > 1) && (vtll::unique>::value)) 145 | [[nodiscard]] auto Get(size_t archIndex) -> std::tuple { 146 | return std::tuple&...>{ Map>()->Get(archIndex)... }; 147 | } 148 | 149 | /// @brief Erase an entity 150 | /// @param index The index of the entity in the archetype. 151 | /// @param slotmaps The slot maps vector of the registry. 152 | void Erase(size_t index, auto& slotmaps) { 153 | Erase2(index, slotmaps); 154 | } 155 | 156 | /// @brief Move components from another archetype to this one. 157 | size_t Move( auto& types, size_t other_index, Archetype& other, auto& slotmaps) { 158 | for( auto& ti : types ) { //go through all maps 159 | if( m_maps.contains(ti) ) m_maps[ti]->copy(other.Map(ti), other_index); //insert the new value 160 | } 161 | other.Erase2(other_index, slotmaps); //erase from old component map 162 | ++other.m_changeCounter; 163 | ++m_changeCounter; 164 | return m_maps[Type()]->size() - 1; //return the index of the new entity 165 | } 166 | 167 | /// @brief Swap two entities in the archetype. 168 | void Swap(ArchetypeAndIndex& slot1, ArchetypeAndIndex& slot2) { 169 | assert( slot1.m_value.m_archetypePtr == slot2.m_value.m_archetypePtr ); 170 | for( auto& map : m_maps ) { 171 | map.second->swap(slot1.m_value.m_archindex, slot2.m_value.m_archindex); 172 | } 173 | std::swap(slot1.m_value.m_archindex, slot2.m_value.m_archindex); 174 | ++m_changeCounter; 175 | } 176 | 177 | /// @brief Clone the archetype. 178 | /// @param other The archetype to clone. 179 | /// @param types The types of the components to clone. 180 | void Clone(Archetype& other, const auto& types) { 181 | for( auto& ti : types ) { //go through all maps 182 | m_types.insert(ti); //add the type to the list 183 | if( other.m_maps.contains(ti) ) m_maps[ti] = other.Map(ti)->clone(); //make a component map like this one 184 | } 185 | } 186 | 187 | /// @brief Get the number of entites in this archetype. 188 | /// @return The number of entities. 189 | size_t Size() { 190 | return m_maps[Type()]->size(); 191 | } 192 | 193 | /// @brief Clear the archetype. 194 | void Clear() { 195 | for( auto& map : m_maps ) { 196 | map.second->clear(); 197 | } 198 | ++m_changeCounter; 199 | } 200 | 201 | /// @brief Print the archetype. 202 | void Print() { 203 | std::cout << "Archetype: " << Hash(m_types) << std::endl; 204 | for( auto ti : m_types ) { 205 | std::cout << "Type: " << ti << " "; 206 | } 207 | std::cout << std::endl; 208 | for( auto& map : m_maps ) { 209 | std::cout << "Map: "; 210 | map.second->print(); 211 | std::cout << std::endl; 212 | } 213 | std::cout << "Entities: "; 214 | auto map = Map(); 215 | for( auto handle : *map ) { 216 | std::cout << handle << " "; 217 | } 218 | std::cout << std::endl << std::endl; 219 | } 220 | 221 | /// @brief Validate the archetype. Make sure all maps have the same size. 222 | void Validate() { 223 | for( auto& map : m_maps ) { 224 | assert( map.second->size() == m_maps[Type()]->size() ); 225 | } 226 | } 227 | 228 | /// @brief Get the change counter of the archetype. It is increased when a change occurs 229 | /// that might invalidate a Ref object, e.g. when an entity is moved to another archetype, or erased. 230 | auto GetChangeCounter() -> size_t { 231 | return m_changeCounter; 232 | } 233 | 234 | /// @brief Get the mutex of the archetype. 235 | /// @return Reference to the mutex. 236 | [[nodiscard]] auto GetMutex() -> mutex_t& { 237 | return m_mutex; 238 | } 239 | 240 | void AddType(size_t ti) { 241 | assert( !m_types.contains(ti) ); 242 | m_types.insert(ti); //add the type to the list 243 | }; 244 | 245 | /// @brief Add a new component to the archetype. 246 | /// @tparam T The type of the component. 247 | template 248 | void AddComponent() { 249 | using T = std::decay_t; //remove pointer or reference 250 | size_t ti = Type(); 251 | assert( !m_types.contains(ti) ); 252 | m_types.insert(ti); //add the type to the list 253 | m_maps[ti] = std::make_unique>(); //create the component map 254 | }; 255 | 256 | /// @brief Add a new component value to the archetype. 257 | /// @param v The component value. 258 | /// @return The index of the component value. 259 | template 260 | auto AddValue( U&& v ) -> size_t { 261 | using T = std::decay_t; 262 | return m_maps[Type()]->push_back(std::forward(v)); //insert the component value 263 | }; 264 | 265 | auto AddEmptyValue( size_t ti ) -> size_t { 266 | return m_maps[ti]->push_back(); //insert the component value 267 | }; 268 | 269 | /// @brief Erase an entity. To ensure thet consistency of the entity indices, the last entity is moved to the erased one. 270 | /// This might result in a reindexing of the moved entity in the slot map. Thus we need a ref to the slot map 271 | /// @param index The index of the entity in the archetype. 272 | /// @param slotmaps Reference to the slot maps vector of the registry. 273 | void Erase2(size_t index, auto& slotmaps) { 274 | size_t last{index}; 275 | for( auto& it : m_maps ) { 276 | last = it.second->erase(index); //Erase from the component map 277 | } 278 | if( index < last ) { 279 | auto& lastHandle = static_cast*>(m_maps[Type()].get())->operator[](index); 280 | slotmaps[lastHandle.GetStorageIndex()].m_slotMap[lastHandle].m_value.m_archIndex = index; 281 | } 282 | ++m_changeCounter; 283 | } 284 | 285 | /// @brief Get the map of the components. 286 | /// @tparam T The type of the component. 287 | /// @return Pointer to the component map. 288 | template 289 | auto Map() -> Vector>* { 290 | auto it = m_maps.find(Type>()); 291 | assert(it != m_maps.end()); 292 | return static_cast>*>(it->second.get()); 293 | } 294 | 295 | /// @brief Get the data of the components. 296 | /// @param ti Type index of the component. 297 | /// @return Pointer to the component map base class. 298 | auto Map(size_t ti) -> VectorBase* { 299 | auto it = m_maps.find(ti); 300 | assert(it != m_maps.end()); 301 | return it->second.get(); 302 | } 303 | 304 | private: 305 | using Size_t = std::conditional_t>; 306 | using Map_t = std::unordered_map>; 307 | mutex_t m_mutex; //mutex for thread safety 308 | Size_t m_changeCounter{0}; //changes invalidate references 309 | std::set m_types; //types of components 310 | Map_t m_maps; //map from type index to component data 311 | 312 | }; //end of Archetype 313 | 314 | } //namespace vecs 315 | -------------------------------------------------------------------------------- /include/old/VECSHashMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace vecs { 6 | 7 | //---------------------------------------------------------------------------------------------- 8 | //Hash Map 9 | 10 | const int HASHMAPTYPE_SEQUENTIAL = 0; 11 | const int HASHMAPTYPE_PARALLEL = 1; 12 | 13 | /// @brief A hash map for storing a map from key to value. 14 | /// The hash map is implemented as a vector of buckets. Each bucket is a list of key-value pairs. 15 | /// Inserting and reading from the hash map is thread-safe, i.e., internally sysnchronized. 16 | 17 | template 18 | class HashMap { 19 | 20 | using mutex_t = std::shared_mutex; ///< Shared mutex type 21 | 22 | /// @brief A key-ptr pair in the hash map. 23 | struct Pair { 24 | std::pair m_keyValue; ///< The key-value pair. 25 | std::unique_ptr m_next{}; ///< Pointer to the next pair. 26 | size_t& Key() { return m_keyValue.first; } 27 | T& Value() { return m_keyValue.second; }; 28 | }; 29 | 30 | /// @brief A bucket in the hash map. 31 | struct Bucket { 32 | std::unique_ptr m_first{}; ///< Pointer to the first pair 33 | mutex_t m_mutex; ///< Mutex for adding something to the bucket. 34 | 35 | Bucket() : m_first{} {}; 36 | Bucket(const Bucket& other) : m_first{} {}; 37 | }; 38 | 39 | using Map_t = std::vector; ///< Type of the map. 40 | 41 | /// @brief Iterator for the hash map. 42 | class Iterator { 43 | public: 44 | /// @brief Constructor for the iterator. 45 | /// @param map The hash map. 46 | /// @param bucketIdx Index of the bucket. 47 | Iterator(HashMap& map, size_t bucketIdx) : m_hashMap{map}, m_bucketIdx{bucketIdx}, m_pair{nullptr} { 48 | if( m_bucketIdx >= m_hashMap.m_buckets.size() ) return; 49 | m_pair = &m_hashMap.m_buckets[m_bucketIdx].m_first; 50 | if( !*m_pair ) Next(); 51 | } 52 | 53 | ~Iterator() = default; ///< Destructor. 54 | auto operator++() { /// @brief Increment the iterator. 55 | if( *m_pair ) { m_pair = &(*m_pair)->m_next; if( *m_pair ) { return *this; } } 56 | Next(); 57 | return *this; 58 | } 59 | 60 | auto& operator*() { return (*m_pair)->m_keyValue; } ///< Dereference the iterator. 61 | auto operator!=(const Iterator& other) -> bool { return m_bucketIdx != other.m_bucketIdx; } ///< Compare the iterator. 62 | 63 | private: 64 | 65 | /// @brief Move to the next bucket. 66 | void Next() { 67 | do {++m_bucketIdx; } 68 | while( m_bucketIdx < m_hashMap.m_buckets.size() && !m_hashMap.m_buckets[m_bucketIdx].m_first ); 69 | if( m_bucketIdx < m_hashMap.m_buckets.size() ) { m_pair = &m_hashMap.m_buckets[m_bucketIdx].m_first; } 70 | } 71 | 72 | HashMap& m_hashMap; ///< Reference to the hash map. 73 | std::unique_ptr* m_pair; ///< Pointer to the pair. 74 | size_t m_bucketIdx; ///< Index of the current bucket. 75 | }; 76 | 77 | public: 78 | 79 | /// @brief Constructor, creates the hash map. 80 | HashMap(size_t bits = 10) : m_buckets{} { 81 | size_t size = (1ull << bits); 82 | for( int i = 0; i < size; ++i ) { 83 | m_buckets.emplace_back(); 84 | } 85 | } 86 | 87 | /// @brief Destructor, destroys the hash map. 88 | ~HashMap() = default; 89 | 90 | /// @brief Find a pair in the bucket. If not found, create a new pair with the given key. 91 | /// @param key Find this key 92 | /// @return Pointer to the value. 93 | T& operator[](size_t key) { 94 | auto& bucket = m_buckets[key & (m_buckets.size()-1)]; 95 | std::unique_ptr* pair; 96 | { 97 | LockGuardShared lock(&bucket.m_mutex); 98 | pair = Find(&bucket.m_first, key); 99 | if( (*pair) != nullptr ) { return (*pair)->Value(); } 100 | } 101 | 102 | LockGuard lock(&bucket.m_mutex); 103 | pair = Find(pair, key); 104 | if( (*pair) != nullptr ) { return (*pair)->Value(); } 105 | *pair = std::make_unique( std::make_pair(key, T{}) ); 106 | m_size++; 107 | return (*pair)->Value(); 108 | } 109 | 110 | /// @brief Get a value from the hash map. 111 | /// @param key The key of the value. 112 | /// @return Pointer to the value. 113 | T* get(size_t key) { 114 | auto& bucket = m_buckets[key & (m_buckets.size()-1)]; 115 | LockGuardShared lock(&bucket.m_mutex); 116 | std::unique_ptr* pair = Find(&bucket.m_first, key); 117 | if( (*pair) != nullptr ) { return (*pair)->Value(); } 118 | return nullptr; 119 | } 120 | 121 | /// @brief Test whether the hash map contains a key. 122 | /// @param key The key to test. 123 | /// @return true if the key is in the hash map, else false. 124 | bool contains(size_t key) { 125 | auto& bucket = m_buckets[key & (m_buckets.size()-1)]; 126 | LockGuardShared lock(&bucket.m_mutex); 127 | std::unique_ptr* pair = Find(&bucket.m_first, key); 128 | return (*pair) != nullptr; 129 | } 130 | 131 | size_t size() { return m_size; } ///< Get the number of items in the hash map. 132 | 133 | auto begin() -> Iterator { return Iterator(*this, 0); } 134 | auto end() -> Iterator { return Iterator(*this, m_buckets.size()); } 135 | 136 | private: 137 | 138 | /// @brief Find a pair in the bucket. 139 | /// @param pair Start of the list of pairs. 140 | /// @param key Find this key 141 | /// @return Pointer to the pair with the key and value, points to the unqiue_ptr of the pair. 142 | auto Find(std::unique_ptr* pair, size_t key) -> std::unique_ptr* { 143 | while( (*pair) != nullptr && (*pair)->Key() != key ) { pair = &(*pair)->m_next; } 144 | return pair; 145 | } 146 | 147 | Map_t m_buckets; ///< The map of buckets. 148 | size_t m_size{0}; ///< The size of the hash map. 149 | }; 150 | 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /include/old/VECSIterator.h: -------------------------------------------------------------------------------- 1 | #ifndef VECSITERATOR_H 2 | #define VECSITERATOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace vecs { 10 | 11 | //------------------------------------------------------------------------- 12 | //iterator 13 | 14 | /** 15 | * \brief Main iterator class template. 16 | * 17 | * The iterator always maintains a tuple with pointers to the current entity, if there is one. 18 | * The pointers are delivered by an accessor function provided by a VecsComponentTable. 19 | */ 20 | template 21 | class VecsIteratorT { 22 | 23 | template friend class VecsRangeBaseClass; 24 | 25 | public: 26 | using handle_type = typename VecsComponentTable < P, vtll::front > >::handle_wrapper_t::handle_type; 27 | 28 | using value_type = vtll::to_tuple< vtll::cat< vtll::tl*, VecsHandleT

>, vtll::to_ref > >; ///< Value type - use refs to deal with atomics and unique_ptr 29 | using reference = value_type&; ///< Reference type 30 | using pointer = value_type*; ///< Pointer to value 31 | using pointer_tuple = vtll::to_tuple< vtll::cat< vtll::tl**, handle_type*>, vtll::to_ptr > >; ///< Pointer type 32 | using iterator_category = std::forward_iterator_tag; ///< Forward iterator 33 | using difference_type = int64_t; ///< Difference type 34 | using accessor = std::function; ///< Function for obtaining pointers to the components 35 | 36 | protected: 37 | type_index_t m_type{}; ///< entity type that this iterator iterates over 38 | size_t m_size{ 0 }; ///< Size of the components, m_current must be smaller than this 39 | size_t m_bit_mask{ 0 }; ///< Size of table segments - 1 , use as bit mask 40 | size_t m_row_size{ 0 }; ///< Size of a row for row layout, or zero for column layout 41 | table_index_t m_current{ 0 }; ///< Current index in the VecsComponentTable 42 | pointer_tuple m_pointers; ///< Pointers to the components 43 | accessor m_accessor; ///< Get pointers to the components 44 | 45 | public: 46 | VecsIteratorT() noexcept = default; ///< Empty constructor is required 47 | 48 | template 49 | VecsIteratorT(const VecsComponentTable& tab, size_t current) noexcept; ///< Constructor 50 | VecsIteratorT(const VecsIteratorT& v) noexcept = default; ///< Copy constructor 51 | VecsIteratorT(VecsIteratorT&& v) noexcept = default; ///< Move constructor 52 | 53 | auto operator=(const VecsIteratorT& v) noexcept -> VecsIteratorT & = default; ///< Copy 54 | auto operator=(VecsIteratorT&& v) noexcept -> VecsIteratorT & = default; ///< Move 55 | 56 | auto operator*() noexcept -> value_type; ///< Access the data 57 | auto operator*() const noexcept -> value_type; ///< Access the const data 58 | auto operator->() noexcept { return operator*(); }; ///< Access 59 | auto operator->() const noexcept { return operator*(); }; ///< Access 60 | 61 | auto operator++() noexcept -> VecsIteratorT&; ///< Increase by 1 62 | auto operator++(int) noexcept -> VecsIteratorT; ///< Increase by 1 63 | auto operator+=(difference_type N) noexcept -> VecsIteratorT&; ///< Increase by N 64 | auto operator+(difference_type N) noexcept -> VecsIteratorT; ///< Increase by N 65 | 66 | bool operator!=(const VecsIteratorT& v) noexcept; ///< Unequal 67 | 68 | friend auto operator==(const VecsIteratorT& v1, const VecsIteratorT& v2) noexcept -> bool { ///< Equal 69 | return v1.m_type == v2.m_type && v1.m_current == v2.m_current; 70 | } 71 | }; 72 | 73 | 74 | /** 75 | * \brief Constructor of class VecsIteratorT. 76 | */ 77 | template 78 | template 79 | inline VecsIteratorT::VecsIteratorT(const VecsComponentTable& tab, size_t current) noexcept 80 | : m_type{ type_index_t{ vtll::index_of::entity_type_list, E>::value } } 81 | , m_size{ tab.size() } 82 | , m_bit_mask{ VecsComponentTable::c_segment_size - 1 } 83 | , m_row_size{ VecsComponentTable::c_row_size } 84 | , m_current{ current } 85 | , m_accessor{ std::bind(VecsComponentTable::template accessor, std::placeholders::_1) } { 86 | 87 | m_pointers = m_current.value < m_size ? m_accessor(m_current) : pointer_tuple{}; ///< Get pointers from accessor 88 | }; 89 | 90 | /** 91 | * \brief Operator for accessing the data. 92 | */ 93 | template 94 | inline auto VecsIteratorT::operator*() noexcept -> value_type { 95 | return std::tuple_cat( 96 | std::make_tuple( 97 | *std::get>>::c_mutex>(m_pointers) 98 | , std::get>>::c_handle>(m_pointers)->load() ) 99 | , vtll::ptr_to_ref_tuple( vtll::sub_tuple<2,std::tuple_size_v>(m_pointers) )); 100 | } 101 | 102 | /** 103 | * \brief Operator for accessing const data. 104 | */ 105 | template 106 | inline auto VecsIteratorT::operator*() const noexcept -> value_type { 107 | return std::tuple_cat( 108 | std::make_tuple( 109 | *std::get>>::c_mutex>(m_pointers) 110 | , std::get>>::c_handle>(m_pointers)->load()) 111 | , vtll::ptr_to_ref_tuple(vtll::sub_tuple<2, std::tuple_size_v>(m_pointers))); 112 | } 113 | 114 | /** 115 | * \brief Increase by 1. 116 | * \returns reference to itself. 117 | */ 118 | template 119 | inline auto VecsIteratorT::operator++() noexcept -> VecsIteratorT& { 120 | m_current.value++; 121 | if ((m_current.value & m_bit_mask) == 0) { //is at the start of a segment, so load pointers from table 122 | m_pointers = m_current.value < m_size ? m_accessor(m_current) : pointer_tuple{}; 123 | } 124 | else { 125 | if (m_row_size > 0) { ///< If row layout 126 | vtll::static_for >( ///< Loop over all components 127 | [&](auto i) { 128 | std::get(m_pointers) = (std::tuple_element_t)((char*)std::get(m_pointers) + m_row_size); 129 | } 130 | ); 131 | } 132 | else { ///< Column layout 133 | vtll::static_for >( [&](auto i) { std::get(m_pointers)++; } ); 134 | } 135 | } 136 | return *this; 137 | } 138 | 139 | /** 140 | * \brief Increase by 1. 141 | * \returns itself. 142 | */ 143 | template 144 | inline auto VecsIteratorT::operator++(int) noexcept -> VecsIteratorT { 145 | auto tmp = *this; 146 | operator++(); 147 | return *this; 148 | } 149 | 150 | /** 151 | * \brief Increase by N. 152 | * \param[in] N Number of rows to skip ahead. 153 | * \returns reference to itself. 154 | */ 155 | template 156 | inline auto VecsIteratorT::operator+=(difference_type N) noexcept -> VecsIteratorT& { 157 | m_current += N; 158 | m_pointers = m_current.value < m_size ? m_accessor(m_current) : pointer_tuple{}; 159 | return *this; 160 | } 161 | 162 | /** 163 | * \brief Increase by N. 164 | * \param[in] N Number of rows to skip ahead. 165 | * \returns itself. 166 | */ 167 | template 168 | inline auto VecsIteratorT::operator+(difference_type N) noexcept -> VecsIteratorT { 169 | m_current += N; 170 | m_pointers = m_current.value < m_size ? m_accessor(m_current) : pointer_tuple{}; 171 | return *this; 172 | } 173 | 174 | /** 175 | * \brief Test for inequality. 176 | * \param[in] v Const reference to the other iterator. 177 | * \returns true, if the iterators are not equal. 178 | */ 179 | template 180 | inline auto VecsIteratorT::operator!=(const VecsIteratorT& v) noexcept -> bool { 181 | return m_type != v.m_type || m_current != v.m_current; 182 | } 183 | 184 | 185 | /** 186 | * \brief General functor type that can hold any function, and depends on a number of component types. 187 | * 188 | * Used in for_each to iterate over entities and call the functor for each entity. 189 | */ 190 | template 191 | struct Functor; 192 | 193 | /** 194 | * \brief Specialization of primary Functor to get the types. 195 | */ 196 | template typename Seq, typename... Cs> 197 | struct Functor> { 198 | using type = void(std::atomic*&, VecsHandleT

, Cs&...); ///< Arguments for the functor 199 | }; 200 | 201 | 202 | /** 203 | * \brief Class representing a range of rows. 204 | */ 205 | template 206 | class VecsRangeBaseClass { 207 | 208 | struct range_t { 209 | VecsIteratorT m_begin{}; 210 | VecsIteratorT m_end{}; 211 | size_t m_size{0}; 212 | 213 | range_t() = default; 214 | range_t(const VecsIteratorT& b, const VecsIteratorT& e) noexcept : m_begin{ b }, m_end{ e }, m_size{ e.m_current - b.m_current } {}; 215 | range_t(VecsIteratorT&& b, VecsIteratorT&& e) noexcept : m_begin{ b }, m_end{ e }, m_size{ e.m_current - b.m_current } {}; 216 | range_t(const range_t&) = default; 217 | range_t(range_t&&) = default; 218 | 219 | auto begin() noexcept { return m_begin; }; 220 | auto end() noexcept { return m_end; }; 221 | auto size() const noexcept { return m_size; }; 222 | }; 223 | 224 | using view_type = decltype(std::views::join(std::declval&>())); 225 | view_type m_view; 226 | size_t m_size{0}; 227 | std::vector m_ranges; 228 | 229 | public: 230 | VecsRangeBaseClass() noexcept = default; 231 | VecsRangeBaseClass(const VecsRangeBaseClass&) noexcept = default; 232 | VecsRangeBaseClass(VecsRangeBaseClass&&) noexcept = default; 233 | 234 | auto operator=(const VecsRangeBaseClass& v) noexcept -> VecsRangeBaseClass & = default; 235 | auto operator=(VecsRangeBaseClass&& v) noexcept -> VecsRangeBaseClass & = default; 236 | 237 | /** 238 | * \brief Create a rang that surpasses the tables of given list of entities ETL. 239 | */ 240 | void compute_ranges() noexcept { 241 | m_ranges.reserve(vtll::size::value); 242 | m_size = 0; 243 | vtll::static_for::value >( ///< Loop over all entity types in ETL 244 | [&](auto i) { 245 | using E = vtll::Nth_type; 246 | VecsComponentTable tab; 247 | auto range = range_t{ VecsIteratorT(tab, 0), VecsIteratorT(tab, tab.size()) }; 248 | m_ranges.push_back(range); 249 | m_size += m_ranges.back().size(); ///< Sum of the rows 250 | } 251 | ); 252 | } 253 | 254 | /** 255 | * \brief Return begin iterator of a range which is the join of all currently stored ranges. 256 | * \returns begin iterator of a range which is the join of all currently stored ranges. 257 | */ 258 | auto begin() noexcept { 259 | if (m_ranges.size() == 0) { compute_ranges(); } 260 | m_view = std::views::join(m_ranges); 261 | return m_view.begin(); 262 | } 263 | 264 | /** 265 | * \brief Return end iterator of a range which is the join of all currently stored ranges. 266 | * \returns end iterator of a range which is the join of all currently stored ranges. 267 | */ 268 | auto end() noexcept { 269 | return m_view.end(); 270 | } 271 | 272 | /** 273 | * \brief Return number if rows that are covered by this range 274 | * \returns number if rows that are covered by this range 275 | */ 276 | auto size() const noexcept { return m_size; } 277 | 278 | /** 279 | * \brief Split the range into N subranges. 280 | * \returns a vector holding N subranges. 281 | */ 282 | auto split(size_t N) noexcept { 283 | std::vector> result; 284 | if (m_ranges.size() == 0) { compute_ranges(); } 285 | if (m_ranges.size() == 0) return result; 286 | 287 | VecsRangeBaseClass new_vecs_range; 288 | 289 | size_t remain = m_size; ///< Remaining entities 290 | size_t num = remain / N; ///< Put the same number in each slot (except maybe last slot) 291 | if (num * N < remain) ++num; ///< We might need one more per slot 292 | size_t need = num; ///< Still need that many entities to complete the next slot 293 | 294 | for( auto& range : m_ranges ) { 295 | size_t available = range.size(); ///< Still available in the current range 296 | size_t current = range.m_begin.m_current; ///< Index of current entity 297 | size_t take = 0; ///< Number of entities to take from the current range 298 | while (available > 0) { ///< Are there entities available in the current range? 299 | take = (need <= available) ? need : available; 300 | 301 | auto b = range.m_begin; ///< Copy begin and end iterators from range 302 | b.m_current = current; ///< Adjust begin and end indices 303 | auto e = range.m_end; 304 | e.m_current = current + take; 305 | new_vecs_range.m_ranges.push_back(range_t{ b, e }); 306 | new_vecs_range.m_size += take; 307 | 308 | current += take; 309 | available -= take; 310 | need -= take; 311 | remain -= take; 312 | 313 | if (need == 0) { ///< Next slot is satisfied 314 | result.push_back(new_vecs_range); ///< Add range to the result 315 | if (remain > 0) { 316 | new_vecs_range = VecsRangeBaseClass{}; ///< Clear the range 317 | need = result.size() < N - 1 ? num : remain; ///< Reset need for next slot 318 | } 319 | } 320 | } 321 | } 322 | return result; 323 | } 324 | 325 | /** 326 | * \brief Loop over the range. 327 | */ 328 | inline auto for_each(std::function::type> f, bool sync = false) noexcept -> void { 329 | auto b = begin(); 330 | auto e = end(); 331 | for (; b != e; ++b) { 332 | auto tuple = *b; 333 | auto& mutex_ptr = std::get< VecsComponentTable>::c_mutex >(tuple); 334 | if (sync) VecsWriteLock::lock(mutex_ptr); ///< Write lock 335 | if (!std::get< VecsComponentTable>::c_handle >(tuple).is_valid()) { 336 | if (sync) VecsWriteLock::unlock(mutex_ptr); ///< unlock 337 | continue; 338 | } 339 | std::apply(f, tuple); ///< Run the function on the references 340 | if (sync) VecsWriteLock::unlock(mutex_ptr); ///< unlock 341 | } 342 | } 343 | 344 | }; 345 | 346 | 347 | /** 348 | * \brief Range over a list of entity types. In this case, entity types are expanded with the tag map. 349 | */ 350 | template 351 | using it_ETL_entity_list = expand_tags< typename VecsRegistryBaseClass

::entity_tag_map, ETL >; 352 | 353 | template 354 | using it_CTL_entity_list = vtll::remove_types< vtll::intersection< ETL >, typename VecsRegistryBaseClass

::entity_tag_list >; 355 | 356 | /** 357 | * \brief Range over a list of entity types. In this case, entity types are expanded with the tag map. 358 | */ 359 | template 360 | requires (is_entity_type_list::value) 361 | class VecsRangeT : public VecsRangeBaseClass< P, it_ETL_entity_list, it_CTL_entity_list > {}; 362 | 363 | 364 | /** 365 | * \brief Range over a set of entity types. Entity types are NOT expanded with the tag map. 366 | */ 367 | template 368 | using it_CTL_entity_types = vtll::remove_types< vtll::intersection< vtll::tl >, typename VecsRegistryBaseClass

::entity_tag_list >; 369 | 370 | /** 371 | * \brief Range over a set of entity types. Entity types are NOT expanded with the tag map. 372 | */ 373 | template 374 | requires (sizeof...(Es) > 0 && are_entity_types) 375 | class VecsRangeT : public VecsRangeBaseClass< P, vtll::tl, it_CTL_entity_types > {}; 376 | 377 | 378 | /** 379 | * \brief Iterator for given component types. 380 | */ 381 | template 382 | using it_ETL_types = vtll::filter_have_all_types< typename VecsRegistryBaseClass

::entity_type_list, vtll::tl >; 383 | 384 | template 385 | using it_CTL_types = vtll::remove_types< vtll::tl, typename VecsRegistryBaseClass

::entity_tag_list >; 386 | 387 | /** 388 | * \brief Range over a set of entities that contain all given component types. 389 | */ 390 | template 391 | requires (sizeof...(Cs) > 0 && are_component_types) 392 | class VecsRangeT : public VecsRangeBaseClass< P, it_ETL_types, it_CTL_types > {}; 393 | 394 | 395 | /** 396 | * \brief Iterator for given entity type that has all tags. 397 | */ 398 | template 399 | using it_ETL_entity_tags = vtll::filter_have_all_types< expand_tags::entity_tag_map, vtll::tl>, vtll::tl >; 400 | 401 | template 402 | using it_CTL_entity_tags = vtll::remove_types< E, typename VecsRegistryBaseClass

::entity_tag_list >; 403 | 404 | /** 405 | * \brief Iterator for given entity type that has all tags. 406 | */ 407 | template 408 | requires (is_entity_type && (sizeof...(Ts) > 0) && are_entity_tags) 409 | class VecsRangeT : public VecsRangeBaseClass< P, it_ETL_entity_tags, it_CTL_entity_tags > {}; 410 | 411 | 412 | /** 413 | * \brief Iterator for all entities. 414 | */ 415 | template 416 | using it_CTL_all_entities = vtll::remove_types< vtll::intersection< typename VecsRegistryBaseClass

::entity_type_list >, typename VecsRegistryBaseClass

::entity_tag_list >; 417 | 418 | /** 419 | * \brief Range over all entity types in VECS. This includes all tag variants. 420 | */ 421 | template 422 | class VecsRangeT

: public VecsRangeBaseClass < P, typename VecsRegistryBaseClass

::entity_type_list, it_CTL_all_entities

> {}; 423 | 424 | } 425 | 426 | 427 | #endif 428 | 429 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(TARGET testvecs) 3 | 4 | set(SOURCE testvecs.cpp testvecs_vecs.cpp) 5 | 6 | set(HEADERS 7 | ${PROJECT_SOURCE_DIR}/include/VECS.h 8 | ${PROJECT_SOURCE_DIR}/include/VECSArchetype.h 9 | ${PROJECT_SOURCE_DIR}/include/VECSHandle.h 10 | ${PROJECT_SOURCE_DIR}/include/VECSMutex.h 11 | ${PROJECT_SOURCE_DIR}/include/VECSSlotMap.h 12 | ${PROJECT_SOURCE_DIR}/include/VECSVector.h 13 | ) 14 | 15 | add_executable(${TARGET} ${SOURCE} ${HEADERS}) 16 | 17 | -------------------------------------------------------------------------------- /tests/testvecs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "VECS.h" 10 | 11 | 12 | void check( bool b, std::string_view msg = "" ) { 13 | if( b ) { 14 | //std::cout << "\x1b[32m passed\n"; 15 | } else { 16 | std::cout << "\x1b[31m failed: " << msg << "\n"; 17 | exit(1); 18 | } 19 | } 20 | 21 | void test_handle() { 22 | std::cout << "\x1b[37m testing handle..."; 23 | 24 | { 25 | vecs::Handle h0; 26 | vecs::Handle h1{1,2}; 27 | vecs::Handle h2{1,2}; 28 | vecs::Handle h3{1,3}; 29 | 30 | vecs::Vector v; 31 | v.push_back( h0 ); 32 | 33 | check( h0.IsValid() == false ); 34 | check( h1.IsValid() == true ); 35 | check( h1.GetIndex() == 1 ); 36 | check( h1.GetVersion() == 2 ); 37 | check( h1.GetStorageIndex() == 0 ); 38 | check( h1 == h2 ); 39 | check( h1 != h3 ); 40 | check( h2 != h3 ); 41 | } 42 | 43 | std::cout << "\x1b[32m passed\n"; 44 | } 45 | 46 | void test_vector() { 47 | std::cout << "\x1b[37m testing vector..."; 48 | { 49 | vecs::Vector vec; 50 | std::vector> v; 51 | v.push_back( vec ); 52 | 53 | vec.push_back( 0 ); 54 | check( vec[0] == 0 ); 55 | check( vec.size() == 1 ); 56 | for( int i=1; i<10000; ++i ) { vec.push_back( i ); } 57 | check( vec.size() == 10000 ); 58 | for( int i=0; i<10000; ++i ) { check( vec[i] == i ); } 59 | int j=0; 60 | for( auto& i : vec ) { check( i == j++ ); } 61 | while( vec.size() > 0 ) { vec.pop_back(); } 62 | check( vec.size() == 0 ); 63 | for( int i=0; i<20000; ++i ) { vec.push_back( i ); } 64 | check( vec.size() == 20000 ); 65 | vec.clear(); 66 | check( vec.size() == 0 ); 67 | for( int i=0; i<30000; ++i ) { vec.push_back( i ); } 68 | check( vec.size() == 30000 ); 69 | for( int i=0; i<1000; ++i ) { 70 | vec.erase( 0 ); 71 | check( vec[0] == 30000 - i - 1); 72 | } 73 | check( vec.size() == 29000 ); 74 | for( int i=0; i<1000; ++i ) { 75 | vec.erase( i ); 76 | check( vec.size() == 29000 - i - 1 ); 77 | } 78 | check( vec.size() == 28000 ); 79 | 80 | vec.clear(); 81 | check( vec.size() == 0 ); 82 | for( int i=0; i<30000; ++i ) { vec.push_back( i ); } 83 | 84 | vecs::Vector vec2; 85 | for( int i=0; i<10000; ++i ) { vec2.copy( &vec, i ); } 86 | for( int i=0; i<10000; ++i ) { 87 | check( vec2[i] == i ); 88 | } 89 | 90 | vec.swap( 0, 1 ); 91 | check( vec[0] == 1 ); 92 | check( vec[1] == 0 ); 93 | 94 | auto newvec = vec.clone(); 95 | check( newvec->size() == 0 ); 96 | 97 | vecs::VectorBase* vb = &vec; 98 | for( int i=0; i<10000; ++i ) { vb->push_back(); } 99 | } 100 | std::cout << "\x1b[32m passed\n"; 101 | } 102 | 103 | void test_slotmap() { 104 | std::cout << "\x1b[37m testing slot map..."; 105 | { 106 | vecs::SlotMap sm(0,6); 107 | std::vector> v; 108 | v.push_back( sm ); 109 | 110 | auto [h1, v1] = sm.Insert(1); 111 | auto [h2, v2] = sm.Insert(2); 112 | auto [h3, v3] = sm.Insert(3); 113 | check( sm.Size() == 3 ); 114 | 115 | check( sm[h1].m_value == 1 ); 116 | check( sm[h1].m_version == 0 ); 117 | 118 | check( sm[h2].m_value == 2 ); 119 | check( sm[h1].m_version == 0 ); 120 | 121 | check( sm[h3].m_value == 3 ); 122 | check( sm[h1].m_version == 0 ); 123 | 124 | sm.Erase( h1 ); 125 | sm.Erase( h2); 126 | check( sm.Size() == 1 ); 127 | check( sm[h3].m_value == 3 ); 128 | 129 | sm.Clear(); 130 | check( sm.Size() == 0 ); 131 | 132 | std::vector handles; 133 | for( int i=0; i<10000; ++i ) { handles.push_back( sm.Insert(i).first ); } 134 | check( sm.Size() == 10000 ); 135 | 136 | for( auto& h : handles ) { sm.Erase( h ); } 137 | check( sm.Size() == 0 ); 138 | 139 | handles.clear(); 140 | for( int i=0; i<10000; ++i ) { handles.push_back( sm.Insert(i).first ); } 141 | check( sm.Size() == 10000 ); 142 | for( int i=0; i<10000; ++i ) { check( sm[handles[i]].m_value == i ) ; } 143 | 144 | } 145 | std::cout << "\x1b[32m passed\n"; 146 | } 147 | 148 | void test_archetype() { 149 | std::cout << "\x1b[37m testing archetype..."; 150 | 151 | { 152 | vecs::Archetype arch; 153 | arch.AddComponent(); 154 | arch.AddComponent(); 155 | arch.AddComponent(); 156 | arch.AddComponent(); 157 | arch.AddComponent(); 158 | 159 | arch.AddValue( vecs::Handle{1,2} ); 160 | arch.AddValue( 1 ); 161 | arch.AddValue( 2.0f ); 162 | arch.AddValue( 'a' ); 163 | arch.AddValue( 3.0 ); 164 | arch.AddValue( std::string("hello") ); 165 | 166 | check( arch.Size() == 1 ); 167 | check( arch.Get(0) == 1 ); 168 | check( arch.Get(0) == 2.0f ); 169 | check( arch.Get(0) == 'a' ); 170 | check( arch.Get(0) == 3.0 ); 171 | check( arch.Get(0) == "hello" ); 172 | 173 | arch.Clear(); 174 | check( arch.Size() == 0 ); 175 | 176 | arch.Insert( vecs::Handle{1,2}, 1, 2.0f, 'a', 3.0, std::string("hello") ); 177 | check( arch.Size() == 1 ); 178 | check( arch.Get(0) == 1 ); 179 | check( arch.Get(0) == 2.0f ); 180 | check( arch.Get(0) == 'a' ); 181 | check( arch.Get(0) == 3.0 ); 182 | check( arch.Get(0) == "hello" ); 183 | 184 | arch.Insert( vecs::Handle{2,3}, 2, 3.0f, 'b', 4.0, std::string("world") ); 185 | check( arch.Size() == 2 ); 186 | check( arch.Get(0) == 1 ); 187 | check( arch.Get(0) == 2.0f ); 188 | check( arch.Get(0) == 'a' ); 189 | check( arch.Get(0) == 3.0 ); 190 | check( arch.Get(0) == "hello" ); 191 | 192 | check( arch.Get(1) == 2 ); 193 | check( arch.Get(1) == 3.0f ); 194 | check( arch.Get(1) == 'b' ); 195 | check( arch.Get(1) == 4.0 ); 196 | check( arch.Get(1) == "world" ); 197 | 198 | arch.Erase( 0 ); 199 | check( arch.Size() == 1 ); 200 | check( arch.Get(0) == 2 ); 201 | check( arch.Get(0) == 3.0f ); 202 | check( arch.Get(0) == 'b' ); 203 | 204 | arch.Erase( 0 ); 205 | check( arch.Size() == 0 ); 206 | } 207 | 208 | { 209 | vecs::Archetype arch; 210 | arch.AddComponent(); 211 | arch.AddComponent(); 212 | arch.AddComponent(); 213 | arch.AddComponent(); 214 | arch.AddComponent(); 215 | 216 | vecs::Archetype::m_iteratingArchetype = &arch; 217 | vecs::Archetype::m_iteratingIndex = 5; 218 | 219 | auto add = [&](int i){ 220 | arch.AddValue( vecs::Handle{1,(size_t)i} ); 221 | arch.AddValue( i ); 222 | arch.AddValue( 2.0f*i ); 223 | arch.AddValue( 'c' ); 224 | arch.AddValue( 3.0*i ); 225 | arch.AddValue( std::string("hello....") ); 226 | }; 227 | 228 | for( int i=0; i<10; ++i ) { 229 | add(i); 230 | } 231 | check( arch.Size() == 10 ); 232 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 233 | arch.Print(); 234 | arch.Erase( 6 ); 235 | check( arch.Size() == 9 ); 236 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 237 | arch.Print(); 238 | arch.Erase( 6 ); 239 | check( arch.Size() == 8 ); 240 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 241 | arch.Print(); 242 | arch.Erase( 6 ); 243 | check( arch.Size() == 7 ); 244 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 245 | arch.Print(); 246 | arch.Erase( 6 ); 247 | check( arch.Size() == 6 ); 248 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 249 | arch.Print(); 250 | 251 | vecs::Archetype::m_iteratingIndex = 5; 252 | arch.Erase( 1 ); 253 | check( arch.Size() == 5 ); 254 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 255 | arch.Print(); 256 | arch.Erase( 2 ); 257 | check( arch.Size() == 4 ); 258 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 259 | arch.Print(); 260 | arch.Erase( 0 ); 261 | check( arch.Size() == 3 ); 262 | std::cout << "\nArchetype size: " << arch.Size() << std::endl; 263 | arch.Print(); 264 | vecs::Archetype::m_gaps.clear(); 265 | } 266 | 267 | { 268 | vecs::Archetype arch, arch2; 269 | arch.AddComponent(); 270 | arch.AddComponent(); 271 | arch.AddComponent(); 272 | arch.AddComponent(); 273 | arch.AddComponent(); 274 | 275 | arch2.AddComponent(); 276 | arch2.AddComponent(); 277 | arch2.AddComponent(); 278 | arch2.AddComponent(); 279 | 280 | arch2.Insert( vecs::Handle{1,2}, 1, 2.0f, 'a', 3.0 ); 281 | check( arch2.Size() == 1 ); 282 | check( arch2.Get(0) == 1 ); 283 | check( arch2.Get(0) == 2.0f ); 284 | check( arch2.Get(0) == 'a' ); 285 | check( arch2.Get(0) == 3.0 ); 286 | 287 | arch2.Insert( vecs::Handle{2,3}, 2, 3.0f, 'b', 4.0 ); 288 | check( arch2.Size() == 2 ); 289 | check( arch2.Get(1) == 2 ); 290 | check( arch2.Get(1) == 3.0f ); 291 | check( arch2.Get(1) == 'b' ); 292 | check( arch2.Get(1) == 4.0 ); 293 | 294 | auto [index, handle] = arch.Move( arch2, 0 ); 295 | check( arch.Size() == 1 ); 296 | check( arch2.Size() == 1 ); 297 | check( index == 0 ); 298 | check( handle == vecs::Handle{2,3} ); 299 | check( arch.Get(0) == 1 ); 300 | check( arch.Get(0) == 2.0f ); 301 | check( arch.Get(0) == 'a' ); 302 | check( arch.Get(0) == 3.0 ); 303 | 304 | auto [index1, handle1] = arch.Move( arch2, 0 ); 305 | check( arch.Size() == 2 ); 306 | check( arch2.Size() == 0 ); 307 | check( index1 == 1 ); 308 | check( handle1 == vecs::Handle{} ); 309 | check( arch.Get(1) == 2 ); 310 | check( arch.Get(1) == 3.0f ); 311 | check( arch.Get(1) == 'b' ); 312 | check( arch.Get(1) == 4.0 ); 313 | 314 | vecs::Archetype arch3; 315 | arch3.Clone( arch, std::vector{} ); 316 | check( arch3.Size() == 0 ); 317 | check( arch3.Has( vecs::Type() ) == true ); 318 | check( arch3.Has( vecs::Type() ) == true ); 319 | check( arch3.Has( vecs::Type() ) == true ); 320 | check( arch3.Has( vecs::Type() ) == true ); 321 | check( arch3.Has( vecs::Type() ) == true ); 322 | check( arch3.Has( vecs::Type() ) == true ); 323 | 324 | vecs::Archetype arch4; 325 | arch4.Clone( arch, std::vector{vecs::Type(), vecs::Type()} ); 326 | check( arch4.Size() == 0 ); 327 | check( arch4.Has( vecs::Type() ) == true ); 328 | check( arch4.Has( vecs::Type() ) == false ); 329 | check( arch4.Has( vecs::Type() ) == true ); 330 | check( arch4.Has( vecs::Type() ) == true ); 331 | check( arch4.Has( vecs::Type() ) == false ); 332 | check( arch4.Has( vecs::Type() ) == true ); 333 | 334 | } 335 | std::cout << "\x1b[32m passed\n"; 336 | 337 | } 338 | 339 | void test_mutex() { 340 | 341 | } 342 | 343 | void test_vecs(); 344 | 345 | void test_registry() { 346 | std::cout << "\x1b[37m testing registry..."; 347 | 348 | test_vecs(); 349 | std::cout << "\x1b[32m passed\n"; 350 | 351 | } 352 | 353 | 354 | int main() { 355 | std::cout << "testing VECS...\n"; 356 | test_handle(); 357 | test_vector(); 358 | test_slotmap(); 359 | test_archetype(); 360 | test_mutex(); 361 | test_registry(); 362 | } 363 | 364 | -------------------------------------------------------------------------------- /tests/testvecs_vecs.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "VECS.h" 11 | 12 | bool boolprint = false; 13 | void check( bool b, std::string_view msg = "" ); 14 | 15 | struct test_struct { 16 | int i; 17 | float f; 18 | }; 19 | using strong_struct = vsty::strong_type_t>; 20 | using strong_int = vsty::strong_type_t>; 21 | 22 | int test1() { 23 | 24 | 25 | if(boolprint) std::cout << "test 1.2 system" << std::endl; 26 | 27 | { 28 | vecs::Registry system; 29 | 30 | //Valid 31 | { 32 | vecs::Handle handle; 33 | check( !handle.IsValid() ); 34 | if(boolprint) std::cout << "Handle size: " << sizeof(handle) << std::endl; 35 | } 36 | 37 | //Insert, Types, Get, Has, Erase, Exists 38 | { 39 | std::string s = "AAA"; 40 | auto hhh = system.Insert(s); 41 | check( system.Exists(hhh) ); 42 | 43 | vecs::Handle handle = system.Insert(5, 5.5f); 44 | check( system.Exists(handle) ); 45 | auto t1 = system.Types(handle); 46 | check( t1.size() == 3 ); //also handle! 47 | std::set types{vecs::Type(), vecs::Type(), vecs::Type()}; 48 | std::ranges::for_each( t1, [&](auto& t){ check( types.contains(t) ); } ); 49 | auto v1 = system.Get(handle); 50 | check( v1 == 5 ); 51 | check( system.Has(handle) ); 52 | system.Erase(handle); 53 | check( !system.Exists(handle) ); 54 | 55 | //vecs::Handle hx = system.Insert(5, 6); //compile error 56 | struct height_t { int i; }; 57 | struct weight_t { int i; }; 58 | vecs::Handle hx1 = system.Insert(5, height_t{6}, weight_t{6}); //works 59 | } 60 | 61 | //Exists 62 | { 63 | auto handle = system.Insert(5, 6.9f, 7.3); 64 | check( system.Exists(handle) ); 65 | auto t2 = system.Types(handle); 66 | std::set types{vecs::Type(), vecs::Type(), vecs::Type(), vecs::Type()}; 67 | std::ranges::for_each( t2, [&](auto& t){ check( types.contains(t) ); } ); 68 | } 69 | 70 | //Get 71 | { 72 | auto handle = system.Insert(5, 6.9f, 7.3); 73 | auto value = system.Get(handle); 74 | auto f1 = value; 75 | value = 10.0f; 76 | check( system.Get(handle) == 10.0f ); 77 | auto c = system.Get(handle); //new component -> change counter goes up 78 | //float val = value; //access old reference -> error 79 | //value = 5.0f; //access old reference -> error 80 | c = 'A'; 81 | check( system.Get(handle) == 'A'); //new component -> change counter goes up 82 | 83 | auto [v2a, v2b] = system.Get(handle); 84 | auto [v3a, v3b] = system.Get(handle); 85 | v3a = 100.0f; 86 | v3b = 101.0; 87 | auto [v4a, v4b] = system.Get(handle); 88 | check( v4a == 100.0f && v4b == 101.0 ); 89 | } 90 | 91 | //Put 92 | { 93 | auto handle = system.Insert(5, 6.9f, 7.3); 94 | system.Put(handle, 50, 69.0f, 73.0); 95 | auto [v5a, v5b, v5c] = system.Get(handle); 96 | check( v5b == 69.0f && v5c == 73.0 ); 97 | std::tuple tup = system.Get(handle); 98 | std::get(tup) = 101.0f; 99 | std::get(tup) = 102.0; 100 | system.Put(handle, tup); 101 | auto [v6a, v6b] = system.Get(handle); 102 | check( v6a == 101.0f && v6b == 102.0 ); 103 | 104 | auto tup2 = system.Get(handle); 105 | int ii = std::get(tup2); 106 | auto [ivalue, fvalue, dvalue] = system.Get(handle); 107 | check( ivalue == 50 && fvalue == 101.0f && dvalue == 102.0 ); 108 | } 109 | 110 | //Has 111 | { 112 | auto handle = system.Insert(5, 6.9f, 7.3); 113 | system.Put(handle, 50, 69.0f, 73.0); 114 | check( system.Has(handle) ); 115 | check( system.Has(handle) ); 116 | check( system.Has(handle) ); 117 | } 118 | 119 | //reference to strong type 120 | { 121 | strong_int si{5}; 122 | strong_struct ss{{10, 6.9f}}; 123 | auto handle = system.Insert(si, ss); 124 | check( system.Has(handle) ); 125 | auto [rsi, rss] = system.Get(handle); 126 | int i = rsi; 127 | strong_struct ss2 = rss; 128 | rss().i = 100; 129 | check( system.Get(handle)().i == 100 ); 130 | rss.Value().i = 101; 131 | check( system.Get(handle)().i == 101 ); 132 | rss().f = 101.0f; 133 | check( system.Get(handle)().f == 101.0f ); 134 | rss.Value().f = 102.0f; 135 | check( system.Get(handle)().f == 102.0f ); 136 | auto mewss = system.Get(handle); 137 | mewss.Value().i = 103; 138 | check( system.Get(handle)().i == 103 ); 139 | mewss.Get() = {104, 204.0f}; 140 | check( system.Get(handle)().i == 104 ); 141 | check( system.Get(handle)().f == 204.0f ); 142 | } 143 | 144 | //Erase components 145 | { 146 | auto handle = system.Insert(5, 6.9f, 7.3); 147 | system.Erase(handle); //remove two components 148 | check( !system.Has(handle) ); 149 | check( !system.Has(handle) ); 150 | check( system.Has(handle) ); 151 | system.Erase(handle); //remove also the last component 152 | check( system.Exists(handle)); //check that the entity still exists 153 | check( !system.Has(handle) ); 154 | } 155 | 156 | //Add components with Put 157 | { 158 | auto handle = system.Insert(5, 6.9f, 7.3); 159 | system.Erase(handle); 160 | check( !system.Has(handle) ); 161 | check( !system.Has(handle) ); 162 | check( !system.Has(handle) ); 163 | system.Put(handle, 3.9); //add a component of type double 164 | check( system.Exists(handle)); //check that the entity still exists 165 | check( system.Has(handle) ); 166 | auto d = system.Get(handle); 167 | auto cc = system.Get(handle); // 168 | cc = 'A'; 169 | } 170 | 171 | //Add components with Get 172 | { 173 | auto handle = system.Insert(5, 6.9f, 7.3); 174 | auto dd = system.Get(handle); // 175 | std::string s = "AAA"; 176 | struct T1 { 177 | const char* m_str; 178 | }; 179 | 180 | system.Put(handle, s, T1{"BBB"}); // 181 | auto [ee, ff] = system.Get(handle); // 182 | check( ee == "AAA" && ff.m_str == std::string{"BBB"} ); 183 | } 184 | 185 | //Erase entity 186 | { 187 | auto handle = system.Insert(5, 6.9f, 7.3); 188 | check( system.Exists(handle) ); 189 | system.Erase(handle); 190 | check( !system.Exists(handle) ); 191 | } 192 | 193 | //Add Tags 194 | { 195 | auto handle1 = system.Insert(5, 6.9f, 7.3); 196 | auto handle2 = system.Insert(6, 7.9f, 8.3); 197 | auto handle3 = system.Insert(7, 8.9f, 9.3); 198 | system.AddTags(handle1, 1ull, 2ull, 3ull); 199 | system.AddTags(handle2, 1ull, 3ull); 200 | system.AddTags(handle3, 2ull, 3ull); 201 | auto tags = system.Types(handle1); 202 | check( tags.size() == 7 ); 203 | for( auto handle : system.template GetView(std::vector{1}) ) { 204 | if(boolprint) std::cout << "Handle (yes 1): "<< handle << std::endl; 205 | } 206 | for( auto handle : system.template GetView(std::vector{1ull}, std::vector{2}) ) { 207 | if(boolprint) std::cout << "Handle (yes 1 no 2): "<< handle << std::endl; 208 | } 209 | } 210 | 211 | //Erase Tags 212 | { 213 | auto handle = system.Insert(5, 6.9f, 7.3); 214 | system.AddTags(handle, 1ull, 2ull, 3ull); 215 | auto tags = system.Types(handle); 216 | check( tags.size() == 7 ); 217 | system.EraseTags(handle, 1ull); 218 | tags = system.Types(handle); 219 | check( tags.size() == 6 ); 220 | system.EraseTags(handle, 2ull, 3ull); 221 | tags = system.Types(handle); 222 | check( tags.size() == 4 ); 223 | } 224 | 225 | //for loop 226 | auto hd1 = system.Insert(1, 10.0f, 10.0); 227 | auto hd2 = system.Insert(2, 20.0f ); 228 | auto hd3 = system.Insert(3, 30.0, std::string("AAA")); 229 | auto hd4 = system.Insert(4, 40.0f, 40.0); 230 | auto hd5 = system.Insert(5); 231 | auto hd6 = system.Insert(6, 60.0f, 60.0); 232 | 233 | int a = 0; 234 | float b = 1.0f; 235 | std::tuple tup3 = {a, b}; 236 | std::get(tup3) = 100; 237 | 238 | //auto hhh = system.Get(hd1); //compile error 239 | 240 | if(boolprint) std::cout << "Loop Handle: " << std::endl; 241 | for( auto handle : system.template GetView() ) { 242 | if(boolprint) std::cout << "Handle: "<< handle << std::endl; 243 | } 244 | 245 | if(boolprint) std::cout << "Loop Handle int& float " << std::endl; 246 | for( auto [handle, i, f] : system.template GetView() ) { 247 | if(boolprint) std::cout << "Handle: "<< handle << " int: " << i << " float: " << f << std::endl; 248 | i = 100; 249 | f = 100.0f; 250 | //auto h2 = system.Insert(5, 5.5f); 251 | } 252 | 253 | if(boolprint) std::cout << "Loop Handle int& float& " << std::endl; 254 | for( auto [handle, i, f] : system.template GetView() ) { 255 | if(boolprint) std::cout << "Handle: "<< handle << " int: " << i << " float: " << f << std::endl; 256 | } 257 | 258 | check( system.Size() > 0 ); 259 | system.Clear(); 260 | check( system.Size() == 0 ); 261 | } 262 | 263 | { 264 | vecs::Registry system; 265 | 266 | std::vector handles; 267 | for( int i=0; i<10; ++i ) { 268 | auto h = system.Insert(i, (float)i, (double)i, 'A', std::string("AAAAAA") ); 269 | handles.push_back(h); 270 | } 271 | system.Print(); 272 | for(auto [h, i, f, d] : system.template GetView() ) { 273 | if (i==1) { 274 | system.Erase(handles[2]); 275 | } 276 | 277 | if( i == 5 || i == 6) { 278 | system.Erase(h); 279 | } 280 | system.Print(); 281 | } 282 | system.Print(); 283 | } 284 | 285 | 286 | return 0; 287 | } 288 | 289 | 290 | size_t test_insert_iterate( vecs::Registry& system, int m ) { 291 | 292 | auto t1 = std::chrono::high_resolution_clock::now(); 293 | 294 | for( int i=0; i() ) { 299 | i = (int)(f + d); 300 | } 301 | 302 | auto t2 = std::chrono::high_resolution_clock::now(); 303 | 304 | return std::chrono::duration_cast(t2 - t1).count(); 305 | } 306 | 307 | 308 | size_t test_insert( vecs::Registry& system, int m ) { 309 | 310 | auto t1 = std::chrono::high_resolution_clock::now(); 311 | 312 | for( int i=0; i(t2 - t1).count(); 318 | } 319 | 320 | 321 | size_t test_iterate( vecs::Registry& system, int m ) { 322 | 323 | auto t1 = std::chrono::high_resolution_clock::now(); 324 | 325 | for( auto [handle, i, f] : system.template GetView() ) { 326 | i = i + (int)f; 327 | f = (float)i; 328 | //system.template Has(handle); 329 | //system.Exists(handle); 330 | } 331 | 332 | auto t2 = std::chrono::high_resolution_clock::now(); 333 | 334 | return std::chrono::duration_cast(t2 - t1).count(); 335 | } 336 | 337 | 338 | void test3( std::string name, bool insert, auto&& job ) { 339 | 340 | size_t duration; 341 | int num = 500000; 342 | 343 | { 344 | if(boolprint) std::cout << "test 3.1 sequential " + name << std::endl; 345 | vecs::Registry system; 346 | if(insert) test_insert(system, num); 347 | duration = job(system, num); 348 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)num << std::endl; 349 | system.Clear(); 350 | if(insert) test_insert(system, num); 351 | duration = job(system, num); 352 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)num << std::endl; 353 | } 354 | 355 | { 356 | if(boolprint) std::cout << "test 3.2 sequential " + name << std::endl; 357 | vecs::Registry system; 358 | if(insert) test_insert(system, num); 359 | duration = job(system, num); 360 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)num << std::endl; 361 | system.Clear(); 362 | if(insert) test_insert(system, num); 363 | duration = job(system, num); 364 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)num << std::endl; 365 | } 366 | 367 | } 368 | 369 | 370 | void test4( std::string name, bool insert, auto&& job ) { 371 | 372 | vecs::Registry system; 373 | 374 | int num = 500000; 375 | auto work = [&](auto& system) { 376 | size_t duration = job(system, num); 377 | }; 378 | 379 | if(insert) test_insert(system, 4*num); 380 | 381 | if(boolprint) std::cout << "test 4.1 parallel " + name << std::endl; 382 | auto t1 = std::chrono::high_resolution_clock::now(); 383 | { 384 | //std::jthread t1{ [&](){ work(system);} }; 385 | //std::jthread t2{ [&](){ work(system);} }; 386 | //std::jthread t3{ [&](){ work(system);} }; 387 | //std::jthread t4{ [&](){ work(system);} }; 388 | } 389 | { 390 | auto t2 = std::chrono::high_resolution_clock::now(); 391 | auto duration = std::chrono::duration_cast(t2 - t1).count(); 392 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)system.Size() << std::endl; 393 | } 394 | 395 | system.Clear(); 396 | if(insert) test_insert(system, 4*num); 397 | 398 | if(boolprint) std::cout << "test 4.2 parallel " + name << std::endl; 399 | t1 = std::chrono::high_resolution_clock::now(); 400 | 401 | { 402 | //std::jthread t1{ [&](){ work(system);} }; 403 | //std::jthread t2{ [&](){ work(system);} }; 404 | //std::jthread t3{ [&](){ work(system);} }; 405 | //std::jthread t4{ [&](){ work(system);} }; 406 | } 407 | { 408 | auto t2 = std::chrono::high_resolution_clock::now(); 409 | auto duration = std::chrono::duration_cast(t2 - t1).count(); 410 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)system.Size() << std::endl; 411 | } 412 | } 413 | 414 | 415 | template 416 | auto SelectRandom(const S &s, size_t n) { 417 | auto it = std::begin(s); 418 | std::advance(it,n); 419 | return it; 420 | } 421 | 422 | 423 | void test5() { 424 | 425 | if(boolprint) std::cout << "test 5 parallel" << std::endl; 426 | 427 | using system_t = vecs::Registry; 428 | using handles_t = std::set; 429 | system_t system; 430 | 431 | std::random_device rd; 432 | std::mt19937 gen(rd()); 433 | std::uniform_real_distribution<> dis(0.0, 1.0); 434 | 435 | auto GetInt = [&]() -> int { return (int)(dis(gen)*1000.0); }; 436 | auto GetFloat = [&]() -> float { return (float)dis(gen)*1000.0f; }; 437 | auto GetDouble = [&]() -> double { return (double)dis(gen)*1000.0; }; 438 | auto GetChar = [&]() -> char { return (char)(dis(gen)*100.0); }; 439 | 440 | std::vector> jobs; 441 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetInt()) ); } ); 442 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetFloat())); } ); 443 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetDouble())); } ); 444 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetChar())); } ); 445 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetInt(), GetFloat())); } ); 446 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetInt(), GetFloat(), GetDouble())); } ); 447 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetInt(), GetFloat(), GetDouble(), GetChar())); } ); 448 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetInt(), GetFloat(), GetDouble(), GetChar(), std::string("1"))); } ); 449 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetFloat(), GetDouble())); } ); 450 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetFloat(), GetDouble(), GetChar())); } ); 451 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetFloat(), GetDouble(), GetChar(), std::string("1"))); } ); 452 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetDouble(), GetChar())); } ); 453 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetDouble(), GetChar(), std::string("1"))); } ); 454 | jobs.push_back( [&](handles_t& hs) { hs.insert( system.Insert(GetChar(), std::string("1"))); } ); 455 | 456 | jobs.push_back( [&](handles_t& handles) { 457 | if( handles.size()>0) { 458 | auto h = SelectRandom(handles, (size_t)(dis(gen))*handles.size()); 459 | auto v = system.Get(*h); 460 | v = GetInt(); 461 | }; 462 | } ); 463 | 464 | jobs.push_back( [&](handles_t& hs) { 465 | if( hs.size()>0) { 466 | auto h = SelectRandom(hs, (size_t)(dis(gen)*hs.size())); 467 | auto v = system.Get(*h); 468 | v = GetFloat(); 469 | }; 470 | } ); 471 | 472 | jobs.push_back( [&](handles_t& hs) { 473 | if( hs.size()>0) { 474 | auto h = SelectRandom(hs, (size_t)(dis(gen)*hs.size())); 475 | auto db = system.Get(*h); 476 | db = GetDouble(); 477 | }; 478 | } ); 479 | 480 | jobs.push_back( [&](handles_t& hs) { 481 | if( hs.size()>0) { 482 | auto h = SelectRandom(hs, (size_t)(dis(gen)*hs.size())); 483 | auto db = system.Get(*h); 484 | }; 485 | } ); 486 | 487 | 488 | int num = 1000000; 489 | auto work = [&](auto& system) { 490 | std::set hs; 491 | 492 | for( int i=0; i(t2 - t1).count(); 513 | if(boolprint) std::cout << "Size: " << system.Size() << " us: " << duration << " us/entity: " << (double)duration/(double)(8*num) << std::endl; 514 | 515 | } 516 | 517 | 518 | void test_vecs() { 519 | test1(); 520 | 521 | test3( "Insert", false, [&](auto& system, int num){ return test_insert(system, num); } ); 522 | test3( "Iterate", true, [&](auto& system, int num){ return test_iterate(system, num); } ); 523 | test3( "Insert + Iterate", false, [&](auto& system, int num){ return test_insert_iterate(system, num); } ); 524 | 525 | //test4( "Insert", false, [&](auto& system, int num){ return test_insert(system, num); } ); 526 | //test4( "Iterate", true, [&](auto& system, int num){ return test_iterate(system, num); } ); 527 | //test4( "Insert + Iterate", false, [&](auto& system, int num){ return test_insert_iterate(system, num); } ); 528 | 529 | /*for( int i=0; i<1000; ++i ) { 530 | std::cout << "test 5 " << i << std::endl; 531 | test5(); 532 | }*/ 533 | } 534 | 535 | --------------------------------------------------------------------------------