├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── cmake └── clang-cxx-dev-tools.cmake ├── conanfile.py ├── example ├── CMakeLists.txt ├── README.md ├── conanfile.txt └── main.cpp └── src ├── CMakeLists.txt ├── component.cpp ├── component.h ├── componentHandle.h ├── componentManager.h ├── componentMask.cpp ├── componentMask.h ├── entity.h ├── entityHandle.h ├── entityManager.cpp ├── entityManager.h ├── entityMap.h ├── nomad.h ├── system.cpp ├── system.h ├── world.cpp └── world.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 2 3 | ColumnLimit: 125 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Platform specific 2 | .DS_Store 3 | .vs 4 | 5 | # Library files 6 | .*.dep 7 | *.a 8 | *.so 9 | *.o 10 | 11 | # Cmake stuff 12 | Makefile 13 | cmake_install.cmake 14 | CMakeCache.txt 15 | install_manifest.txt 16 | cmake-build-debug 17 | src/CMakeFiles 18 | src/compile_commands.json 19 | 20 | #CLion 21 | .idea 22 | 23 | # Build folders 24 | example/build 25 | build 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Niko Savas 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 | # NomadECS 2 | Nomad ECS is an entity component system implemention. More information about this project can be found on its homepage: https://savas.ca/nomad. Star this repo if you're a fan! 3 | 4 | ## Install 5 | This repository can be built by itself, but the easiest way to use it is alongside [Conan.io](https://conan.io/) 6 | 7 | The package is hosted on Bintray: https://bintray.com/taurheim/nomad/NomadECS:taurheim 8 | 9 | 1. Follow the instructions here: https://docs.conan.io/en/latest/installation.html 10 | 1. Run `conan remote add nomad https://api.bintray.com/conan/taurheim/nomad` 11 | 12 | Try building the example project by following instructions in the `example/` folder 13 | 14 | ## Disclaimer 15 | This repository is still a work in progress - It's actually a simplified port of the more complex game engine I'm developing. This means that it might not cover every edge case or be the most performant in every scenario, instead it is primarily designed to show off ECS concepts and to accompany my blog posts at https://savas.ca/nomad. 16 | 17 | Among the things still left to be done in this repo are: 18 | - Fix memory ownership and add destructors 19 | - Add unit tests 20 | - Add component garbage collection (+ Blog post) 21 | - Write demo game (+ Blog post) 22 | - Support multiple worlds (+ Blog post) 23 | 24 | Feel free to add issues for features that need adding or bugs that come up! 25 | -------------------------------------------------------------------------------- /cmake/clang-cxx-dev-tools.cmake: -------------------------------------------------------------------------------- 1 | # Additional targets to perform clang-format/clang-tidy 2 | # Get all project files 3 | file(GLOB_RECURSE 4 | ALL_CXX_SOURCE_FILES 5 | *.cpp) 6 | file(GLOB_RECURSE 7 | ALL_CXX_HEADERS 8 | *.h) 9 | 10 | # Adding clang-format target if executable is found 11 | find_program(CLANG_FORMAT_EXE "clang-format") 12 | if(CLANG_FORMAT_EXE) 13 | add_custom_target( 14 | clang-format 15 | COMMAND ${CLANG_FORMAT_EXE} 16 | -i 17 | -style=file 18 | ${ALL_CXX_SOURCE_FILES} 19 | ${ALL_CXX_HEADERS} 20 | ) 21 | endif() 22 | 23 | # Adding clang-tidy target if executable is found 24 | find_program(CLANG_TIDY_EXE "clang-tidy") 25 | if(CLANG_TIDY_EXE) 26 | add_custom_target( 27 | clang-tidy 28 | COMMAND ${CLANG_TIDY_EXE} 29 | -checks "*,-llvm-header-guard,-google-build-using-namespace,-clang-analyzer-alpha.clone.CloneChecker,-google-runtime-int,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-clang-analyzer-alpha.deadcode.UnreachableCode,-misc-use-after-move,-cppcoreguidelines-pro-type-vararg,-modernize-use-emplace,-cert-err60-cpp,-fuchsia-overloaded-operator,-misc-non-private-member-variables-in-classes,-llvm-include-order" 30 | -p "." 31 | -header-filter=.* 32 | ${ALL_CXX_SOURCE_FILES} 33 | ) 34 | endif() 35 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile, CMake, tools 2 | 3 | 4 | class NomadEcsConan(ConanFile): 5 | name = "NomadECS" 6 | version = "0.0.5" 7 | license = "MIT" 8 | url = "https://github.com/taurheim/nomadecs" 9 | homepage = "https://savas.ca/nomad" 10 | blog = "https://medium.com/@savas" 11 | description = "Barebones entity component system for game engines" 12 | settings = "os", "compiler", "build_type", "arch" 13 | options = {"shared": [True, False]} 14 | default_options = "shared=False" 15 | generators = "cmake" 16 | exports_sources = "src/*" 17 | 18 | def build(self): 19 | cmake = CMake(self) 20 | cmake.configure(source_folder="src") 21 | cmake.build() 22 | 23 | def package(self): 24 | self.copy("*.h", dst="include/nomadecs", src="src") 25 | self.copy("*nomadecs.lib", dst="lib", keep_path=False) 26 | self.copy("*.dll", dst="bin", keep_path=False) 27 | self.copy("*.so", dst="lib", keep_path=False) 28 | self.copy("*.dylib", dst="lib", keep_path=False) 29 | self.copy("*.a", dst="lib", keep_path=False) 30 | 31 | def package_info(self): 32 | self.cpp_info.libs = ["nomadecs"] 33 | 34 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(NomadEcsExample) 2 | cmake_minimum_required(VERSION 3.6) 3 | set(CMAKE_CXX_STANDARD 14) 4 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 5 | set(CMAKE_CXX_EXTENSIONS OFF) 6 | 7 | # Download automatically, you can also just copy the conan.cmake file 8 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") 9 | message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") 10 | file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.9/conan.cmake" 11 | "${CMAKE_BINARY_DIR}/conan.cmake") 12 | endif() 13 | 14 | include(${CMAKE_BINARY_DIR}/conan.cmake) 15 | conan_cmake_run(CONANFILE conanfile.txt 16 | BASIC_SETUP 17 | BUILD missing) 18 | 19 | add_executable(nomadexample main.cpp) 20 | target_link_libraries(nomadexample ${CONAN_LIBS}) 21 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Building the example project 2 | Make sure you've added the remote into conan (see the main README.md) before building the example project 3 | 4 | ## CMake & GCC (Unix Shell or IDE with CMake support) 5 | If using unix shell: 6 | 7 | ``` 8 | mkdir build && cd build 9 | conan install .. 10 | cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release 11 | cmake --build . 12 | ``` 13 | 14 | If you're using windows, both [Visual Studio 2017](https://docs.microsoft.com/en-us/cpp/ide/cmake-tools-for-visual-cpp?view=vs-2017) and [CLion](https://www.jetbrains.com/help/clion/quick-tutorial-on-configuring-clion-on-windows.html) have CMake support. 15 | 16 | ## MSVC (Visual Studio 2017) 17 | Follow instructions here: https://docs.conan.io/en/latest/integrations/visual_studio.html 18 | 19 | Make sure to change the `[generators]` to `visual_studio` in `conanfile.txt` -------------------------------------------------------------------------------- /example/conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | NomadECS/0.0.5@taurheim/testing 3 | 4 | [generators] 5 | cmake 6 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "nomadecs/nomad.h" 4 | 5 | using namespace nomad; 6 | 7 | struct Position : Component { 8 | Position(float x): x(x) {}; 9 | float x; 10 | }; 11 | 12 | class Wind : public System { 13 | public: 14 | Wind() { 15 | signature.addComponent(); 16 | } 17 | 18 | void update(int dt) { 19 | for (auto & entity : registeredEntities) { 20 | ComponentHandle position; 21 | parentWorld->unpack(entity, position); 22 | 23 | // Move 1 every second 24 | position->x += 1.0f * (dt / 1000.0f); 25 | 26 | // Print entity position 27 | std::cout << "Entity " << entity.id << ": " << position->x << std::endl; 28 | } 29 | } 30 | }; 31 | 32 | int main() { 33 | // Create the basic building blocks 34 | auto entityManager = std::make_unique(); 35 | auto world = std::make_unique(std::move(entityManager)); 36 | 37 | // Add systems 38 | std::unique_ptr wind = std::make_unique(); 39 | world->addSystem(std::move(wind)); 40 | 41 | // Initialize game 42 | world->init(); 43 | 44 | // Add an entity with a position 45 | auto tumbleweed = world->createEntity(); 46 | tumbleweed.addComponent(Position(0)); 47 | 48 | // Run game for "1 second at 50fps" 49 | for(int i = 0; i < 50; i++) { 50 | world->update(20); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(nomadecs CXX) 3 | 4 | # Include custom cmake rules 5 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 6 | set(CMAKE_CXX_STANDARD 14) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | set(CMAKE_CXX_EXTENSIONS OFF) 9 | include(../cmake/clang-cxx-dev-tools.cmake OPTIONAL) 10 | 11 | add_library(nomadecs 12 | component.cpp 13 | entity.h 14 | componentManager.h 15 | system.cpp 16 | world.cpp 17 | world.h 18 | componentMask.cpp 19 | componentMask.h 20 | entityManager.cpp 21 | entityManager.h 22 | entityHandle.h 23 | componentHandle.h) 24 | -------------------------------------------------------------------------------- /src/component.cpp: -------------------------------------------------------------------------------- 1 | #include "component.h" 2 | 3 | namespace nomad { 4 | int ComponentCounter::familyCounter = 0; 5 | } // namespace nomad 6 | -------------------------------------------------------------------------------- /src/component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "componentManager.h" 4 | #include "entity.h" 5 | /* 6 | This is the base implementation of a component. To create your own component: 7 | 8 | struct Position : Component { 9 | int x; 10 | int y; 11 | } 12 | 13 | What is a component? 14 | https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5 15 | 16 | What is a component family? 17 | https://medium.com/@savas/nomad-game-engine-part-6-the-world-303d305f55cb 18 | */ 19 | namespace nomad { 20 | struct ComponentCounter { 21 | static int familyCounter; 22 | }; 23 | 24 | template 25 | struct Component { 26 | static inline int family() { 27 | static int family = ComponentCounter::familyCounter++; 28 | return family; 29 | } 30 | }; 31 | 32 | // Util method for getting family given type 33 | template 34 | static int GetComponentFamily() { 35 | return Component::type>::family(); 36 | } 37 | } // namespace nomad 38 | -------------------------------------------------------------------------------- /src/componentHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "componentManager.h" 3 | #include "entity.h" 4 | 5 | /* 6 | * This is the implementation for a component handle. With this implementation, 7 | * a component handle is only valid for the update that it is fetched on. 8 | * Holding on to a handle will cause bugs. 9 | * 10 | * What is a handle? 11 | * https://medium.com/@savas/nomad-game-engine-part-4-2-adding-handles-8d299d80c7d0 12 | * 13 | * How do I use a component handle? 14 | * 15 | * There are two ways to access a component using the handle. Access members of 16 | * the component using the arrow operator: 17 | * 18 | * PositionHandle handle; 19 | * handle->x++; 20 | * 21 | */ 22 | namespace nomad { 23 | template 24 | struct ComponentHandle { 25 | using ExposedComponentType = typename ComponentManager::LookupType; 26 | 27 | Entity owner; 28 | ExposedComponentType *component; 29 | ComponentManager *manager; 30 | 31 | // Empty constructor used for World::unpack() 32 | ComponentHandle(){}; 33 | ComponentHandle(Entity owner, ExposedComponentType *component, ComponentManager *manager) { 34 | this->owner = owner; 35 | this->component = component; 36 | this->manager = manager; 37 | } 38 | 39 | // handle->member is the same as handle.component->member 40 | ExposedComponentType *operator->() const { return component; } 41 | 42 | void destroy() { manager->destroyComponent(owner); } 43 | }; 44 | } // namespace nomad -------------------------------------------------------------------------------- /src/componentManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "entity.h" 5 | #include "entityMap.h" 6 | 7 | /* 8 | * This is a generic component manager definition. It uses AoS storage for 9 | * simplicity. A more detailed description of design choices can be found here: 10 | * 11 | * https://medium.com/@savas/nomad-game-engine-part-3-the-big-picture-743cec145685 12 | * https://medium.com/@savas/nomad-game-engine-part-4-1-generic-component-managers-dbe376f10836 13 | * 14 | * What is LookupType? 15 | * LookupType exists to allow for component managers to be extended for 16 | * different storage solutions. Sometimes, the type that is used to instantiate 17 | * a component is different than the type that gets interacted with during the 18 | * game. For example, you might have a sprite component which uses a file path 19 | * to instantiate but from then on holds on to an SDL reference. More detailed 20 | * examples of the uses of LookupType are coming soon. 21 | * 22 | * 23 | */ 24 | namespace nomad { 25 | template 26 | struct ComponentData { 27 | unsigned int size = 1; 28 | std::array *data; 29 | }; 30 | 31 | class BaseComponentManager { 32 | public: 33 | BaseComponentManager() = default; 34 | virtual ~BaseComponentManager() = default; 35 | BaseComponentManager(const BaseComponentManager &) = default; 36 | BaseComponentManager &operator=(const BaseComponentManager &) = default; 37 | BaseComponentManager(BaseComponentManager &&) = default; 38 | BaseComponentManager &operator=(BaseComponentManager &&) = default; 39 | }; 40 | 41 | template 42 | class ComponentManager : public BaseComponentManager { 43 | public: 44 | using LookupType = ComponentType; 45 | 46 | ComponentManager() { 47 | componentData.data = static_cast *>(malloc(sizeof(ComponentType) * 1024)); 48 | } 49 | ComponentInstance addComponent(Entity entity, ComponentType &component) { 50 | ComponentInstance newInstance = componentData.size; 51 | componentData.data->at(newInstance) = component; 52 | entityMap.add(entity, newInstance); 53 | componentData.size++; 54 | return newInstance; 55 | } 56 | 57 | void destroyComponent(Entity entity) { 58 | // TODO(taurheim) write a blog post about delayed garbage collection 59 | // For now, there's a bug with concurrency 60 | ComponentInstance instance = entityMap.getInstance(entity); 61 | 62 | // Move last component to the deleted position to maintain data coherence 63 | ComponentInstance lastComponent = componentData.size - 1; 64 | componentData.data[instance] = componentData.data[lastComponent]; 65 | Entity lastEntity = entityMap.getEntity(lastComponent); 66 | 67 | // Update our map 68 | entityMap.remove(entity); 69 | entityMap.update(lastEntity, instance); 70 | 71 | componentData.size--; 72 | } 73 | 74 | LookupType *lookup(Entity entity) { 75 | ComponentInstance instance = entityMap.getInstance(entity); 76 | return &componentData.data->at(instance); 77 | } 78 | 79 | private: 80 | ComponentData componentData; 81 | EntityMap entityMap; 82 | }; 83 | } // namespace nomad 84 | -------------------------------------------------------------------------------- /src/componentMask.cpp: -------------------------------------------------------------------------------- 1 | #include "componentMask.h" 2 | #include "nomad.h" 3 | 4 | namespace nomad { 5 | bool ComponentMask::isNewMatch(nomad::ComponentMask oldMask, nomad::ComponentMask systemMask) { 6 | return matches(systemMask) && !oldMask.matches(systemMask); 7 | } 8 | 9 | bool ComponentMask::isNoLongerMatched(nomad::ComponentMask oldMask, nomad::ComponentMask systemMask) { 10 | return oldMask.matches(systemMask) && !matches(systemMask); 11 | } 12 | 13 | bool ComponentMask::matches(nomad::ComponentMask systemMask) { return ((mask & systemMask.mask) == systemMask.mask); } 14 | } // namespace nomad 15 | -------------------------------------------------------------------------------- /src/componentMask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | 4 | /* 5 | * ComponentMasks are used by systems as a way of denoting which components an 6 | * entity needs for the system to run on it. For example, a system that updates 7 | * position based on velocity might have a mask that included both the Position 8 | * component and the Velocity component. 9 | * 10 | * For a more detailed explanation: 11 | * https://medium.com/@savas/nomad-game-engine-part-6-the-world-303d305f55cb 12 | */ 13 | namespace nomad { 14 | struct ComponentMask { 15 | unsigned int mask = 0; 16 | 17 | template 18 | void addComponent() { 19 | mask |= (1 << GetComponentFamily()); 20 | } 21 | 22 | template 23 | void removeComponent() { 24 | mask &= ~(1 << GetComponentFamily()); 25 | } 26 | 27 | bool isNewMatch(ComponentMask oldMask, ComponentMask systemMask); 28 | 29 | bool isNoLongerMatched(ComponentMask oldMask, ComponentMask systemMask); 30 | 31 | bool matches(ComponentMask systemMask); 32 | }; 33 | } // namespace nomad 34 | -------------------------------------------------------------------------------- /src/entity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * An entity is just an id. For a more detailed explanation read: 5 | * https://medium.com/@savas/nomad-game-engine-part-2-ecs-9132829188e5 6 | */ 7 | namespace nomad { 8 | class World; 9 | struct Entity { 10 | int id; 11 | 12 | // To put it in maps 13 | friend bool operator<(const Entity &l, const Entity &r) { return l.id < r.id; } 14 | }; 15 | } // namespace nomad 16 | -------------------------------------------------------------------------------- /src/entityHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "componentHandle.h" 3 | #include "entity.h" 4 | #include "world.h" 5 | 6 | /* 7 | * An entity handle provides an easy interface for interactions with an entity. 8 | * Even though the entities' components are stored in completely different 9 | * places, the entityHandle lets them all be accessed as if they were part of 10 | * the entity itself. 11 | * 12 | * Read more here: 13 | * https://medium.com/@savas/nomad-game-engine-part-4-2-adding-handles-8d299d80c7d0 14 | */ 15 | namespace nomad { 16 | struct EntityHandle { 17 | Entity entity; 18 | World *world; 19 | 20 | void destroy() { world->destroyEntity(entity); } 21 | 22 | template 23 | void addComponent(ComponentType &&component) { 24 | world->addComponent(entity, std::forward(component)); 25 | } 26 | 27 | template 28 | void removeComponent() { 29 | world->removeComponent(entity); 30 | } 31 | 32 | template 33 | ComponentHandle getComponent() { 34 | ComponentHandle handle; 35 | world->unpack(entity, handle); 36 | return handle; 37 | } 38 | }; 39 | } // namespace nomad -------------------------------------------------------------------------------- /src/entityManager.cpp: -------------------------------------------------------------------------------- 1 | #include "entityManager.h" 2 | 3 | namespace nomad { 4 | Entity EntityManager::createEntity() { 5 | lastEntity++; 6 | return {lastEntity}; 7 | } 8 | 9 | void EntityManager::destroy(nomad::Entity entity) { 10 | // Do nothing for now. This will be covered in a future blog post. 11 | } 12 | } // namespace nomad 13 | -------------------------------------------------------------------------------- /src/entityManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "entity.h" 3 | 4 | /* 5 | * The entity manager exists to keep track of entity IDs to make sure that 6 | * conflicts don't happen. Read more here: 7 | * https://medium.com/@savas/nomad-game-engine-part-3-the-big-picture-743cec145685 8 | */ 9 | namespace nomad { 10 | class EntityManager { 11 | public: 12 | Entity createEntity(); 13 | void destroy(Entity entity); 14 | 15 | private: 16 | int lastEntity = 0; 17 | }; 18 | } // namespace nomad 19 | -------------------------------------------------------------------------------- /src/entityMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "entity.h" 5 | 6 | /* 7 | * Effectively a bidirectional map 8 | * Entity <-> ComponentInstance 9 | */ 10 | 11 | namespace nomad { 12 | const int MAX_NUMBER_OF_COMPONENTS = 1024; 13 | using ComponentInstance = unsigned int; 14 | 15 | struct EntityMap { 16 | Entity getEntity(ComponentInstance instance) { return instanceToEntity.at(instance); } 17 | 18 | ComponentInstance getInstance(Entity entity) { return entityToInstance.at(entity); } 19 | 20 | void add(Entity entity, ComponentInstance instance) { 21 | // TODO (taurheim) Error when called on an entity already in the map 22 | entityToInstance.insert({entity, instance}); 23 | instanceToEntity.at(instance) = entity; 24 | } 25 | 26 | void update(Entity entity, ComponentInstance instance) { 27 | entityToInstance.at(entity) = instance; 28 | instanceToEntity.at(instance) = entity; 29 | } 30 | 31 | void remove(Entity entity) { entityToInstance.erase(entity); } 32 | 33 | std::map entityToInstance; 34 | std::array instanceToEntity; 35 | }; 36 | } // namespace nomad 37 | -------------------------------------------------------------------------------- /src/nomad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "component.h" 3 | #include "componentHandle.h" 4 | #include "componentManager.h" 5 | #include "entity.h" 6 | #include "entityHandle.h" 7 | #include "system.h" 8 | #include "world.h" 9 | -------------------------------------------------------------------------------- /src/system.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "system.h" 3 | 4 | namespace nomad { 5 | void System::registerWorld(World *world) { parentWorld = world; } 6 | 7 | void System::registerEntity(nomad::Entity const &entity) { registeredEntities.push_back(entity); } 8 | 9 | void System::unRegisterEntity(nomad::Entity const &entity) { 10 | for (auto it = registeredEntities.begin(); it != registeredEntities.end(); ++it) { 11 | Entity e = *it; 12 | if (e.id == entity.id) { 13 | registeredEntities.erase(it); 14 | return; 15 | } 16 | } 17 | } 18 | 19 | ComponentMask System::getSignature() { return signature; } 20 | } // namespace nomad 21 | -------------------------------------------------------------------------------- /src/system.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "componentMask.h" 6 | #include "entity.h" 7 | 8 | /* 9 | * Systems are where the bulk of the game interaction code goes. Read more here: 10 | * 11 | * https://medium.com/@savas/nomad-game-engine-part-3-the-big-picture-743cec145685 12 | * https://medium.com/@savas/nomad-game-engine-part-5-systems-b7e86b572d7b 13 | */ 14 | namespace nomad { 15 | class World; 16 | 17 | class System { 18 | public: 19 | System() = default; 20 | virtual ~System() = default; 21 | System(const System &) = default; 22 | System &operator=(const System &) = default; 23 | System(System &&) = default; 24 | System &operator=(System &&) = default; 25 | 26 | /* 27 | * Called before the game starts but after the world initializes 28 | */ 29 | virtual void init(){}; 30 | 31 | /* 32 | * Called every game update 33 | */ 34 | virtual void update(int dt){}; 35 | 36 | /* 37 | * Called every frame 38 | */ 39 | virtual void render(){}; 40 | 41 | /* 42 | * When a system is added to the world, the world will register itself 43 | */ 44 | void registerWorld(World *world); 45 | 46 | /* 47 | * When a component is added such that this system should begin acting on it, 48 | * register will be called. 49 | */ 50 | void registerEntity(Entity const &entity); 51 | 52 | /* 53 | * If a component is removed from an entity such that the system should stop 54 | * acting on it, unRegister will be called. 55 | */ 56 | void unRegisterEntity(Entity const &entity); 57 | ComponentMask getSignature(); 58 | 59 | protected: 60 | std::vector registeredEntities; 61 | World *parentWorld; 62 | ComponentMask signature; 63 | }; 64 | } // namespace nomad 65 | -------------------------------------------------------------------------------- /src/world.cpp: -------------------------------------------------------------------------------- 1 | #include "world.h" 2 | #include 3 | #include "entityHandle.h" 4 | 5 | namespace nomad { 6 | World::World(std::unique_ptr entityManager) : entityManager(std::move(entityManager)) {} 7 | 8 | void World::init() { 9 | for (auto &system : systems) { 10 | system->init(); 11 | } 12 | } 13 | 14 | void World::update(int dt) { 15 | // TODO(taurheim) check to make sure that the world has called init 16 | for (auto &system : systems) { 17 | system->update(dt); 18 | } 19 | } 20 | 21 | void World::render() { 22 | for (auto &system : systems) { 23 | system->render(); 24 | } 25 | } 26 | 27 | EntityHandle World::createEntity() { return {entityManager->createEntity(), this}; } 28 | 29 | void World::destroyEntity(nomad::Entity entity) { 30 | for (auto &system : systems) { 31 | system->unRegisterEntity(entity); 32 | } 33 | 34 | entityManager->destroy(entity); 35 | } 36 | 37 | void World::addSystem(std::unique_ptr system) { 38 | system->registerWorld(this); 39 | systems.push_back(std::move(system)); 40 | } 41 | 42 | void World::updateEntityMask(nomad::Entity const &entity, nomad::ComponentMask oldMask) { 43 | ComponentMask newMask = entityMasks[entity]; 44 | 45 | for (auto &system : systems) { 46 | ComponentMask systemSignature = system->getSignature(); 47 | if (newMask.isNewMatch(oldMask, systemSignature)) { 48 | // We match but didn't match before 49 | system->registerEntity(entity); 50 | } else if (newMask.isNoLongerMatched(oldMask, systemSignature)) { 51 | system->unRegisterEntity(entity); 52 | } 53 | } 54 | } 55 | } // namespace nomad 56 | -------------------------------------------------------------------------------- /src/world.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "component.h" 4 | #include "componentHandle.h" 5 | #include "componentManager.h" 6 | #include "componentMask.h" 7 | #include "entity.h" 8 | #include "entityManager.h" 9 | #include "system.h" 10 | /* 11 | * The world is a way of linking all of the disparate parts of the ECS. 12 | * Users of the engine/ECS primarily interact with the world and handles. 13 | * Handles are exclusively built by the world. 14 | * 15 | * https://medium.com/@savas/nomad-game-engine-part-3-the-big-picture-743cec145685 16 | * https://medium.com/@savas/nomad-game-engine-part-6-the-world-303d305f55cb 17 | * 18 | * TODO write blog post about multiple worlds 19 | */ 20 | 21 | namespace nomad { 22 | struct EntityHandle; 23 | class World { 24 | public: 25 | explicit World(std::unique_ptr entityManager); 26 | 27 | /* 28 | * Should be called before the first update, but after instantiation 29 | */ 30 | void init(); 31 | 32 | /* 33 | * Update game logic. This is unrelated to a frame 34 | * https://gafferongames.com/post/fix_your_timestep/ 35 | */ 36 | void update(int dt); 37 | /* 38 | * Render a frame. 39 | */ 40 | void render(); 41 | EntityHandle createEntity(); 42 | void addSystem(std::unique_ptr system); 43 | void destroyEntity(Entity entity); 44 | 45 | /* 46 | * This is only necessary for bridge component managers. 47 | * TODO write blog post on bridge component managers 48 | */ 49 | template 50 | void addCustomComponentManager(std::unique_ptr> manager) { 51 | int family = GetComponentFamily(); 52 | if (family >= componentManagers.size()) { 53 | componentManagers.resize(family + 1); 54 | } 55 | componentManagers[family] = manager; 56 | } 57 | 58 | template 59 | void addComponent(Entity const &entity, ComponentType &&component) { 60 | ComponentManager *manager = getComponentManager(); 61 | manager->addComponent(entity, component); 62 | 63 | ComponentMask oldMask = entityMasks[entity]; 64 | entityMasks[entity].addComponent(); 65 | 66 | updateEntityMask(entity, oldMask); 67 | } 68 | 69 | template 70 | void removeComponent(Entity const &entity) { 71 | ComponentManager *manager = getComponentManager(); 72 | ComponentHandle component = manager->lookup(entity); 73 | component.destroy(); 74 | 75 | ComponentMask oldMask = entityMasks[entity]; 76 | entityMasks[entity].removeComponent(); 77 | 78 | updateEntityMask(entity, oldMask); 79 | } 80 | 81 | template 82 | void unpack(Entity e, ComponentHandle &handle, ComponentHandle &... args) { 83 | typedef ComponentManager ComponentManagerType; 84 | auto mgr = getComponentManager(); 85 | handle = ComponentHandle(e, mgr->lookup(e), mgr); 86 | 87 | // Recurse 88 | unpack(e, args...); 89 | } 90 | 91 | // Base case 92 | template 93 | void unpack(Entity e, ComponentHandle &handle) { 94 | typedef ComponentManager ComponentManagerType; 95 | auto mgr = getComponentManager(); 96 | handle = ComponentHandle(e, mgr->lookup(e), mgr); 97 | } 98 | 99 | private: 100 | std::unique_ptr entityManager; 101 | std::vector> systems; 102 | std::vector> componentManagers; 103 | std::map entityMasks; 104 | 105 | void updateEntityMask(Entity const &entity, ComponentMask oldMask); 106 | 107 | template 108 | ComponentManager *getComponentManager() { 109 | // Need to make sure we actually have a component manager. 110 | // TODO(taurheim) this is a performance hit every time we add and remove a component 111 | int family = GetComponentFamily(); 112 | 113 | if (family >= componentManagers.size()) { 114 | componentManagers.resize(family + 1); 115 | } 116 | 117 | if (!componentManagers[family]) { 118 | componentManagers[family] = std::make_unique>(); 119 | } 120 | 121 | return static_cast *>(componentManagers[family].get()); 122 | } 123 | }; 124 | } // namespace nomad 125 | --------------------------------------------------------------------------------