Created
July 7, 2017 08:27
-
-
Save TiliSleepStealer/c516016698677ccc37d21297646f1c97 to your computer and use it in GitHub Desktop.
single header entity component system
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <memory> | |
#include <algorithm> | |
#include <bitset> | |
#include <array> | |
#include <cassert> | |
#include <type_traits> | |
// We will implement groups by giving a group bitset to every entity, | |
// and storing entity pointers in the entity manager. | |
struct Component; | |
class Entity; | |
class Manager; | |
using ComponentID = std::size_t; | |
// Let's create a typedef for our group type... | |
using Group = std::size_t; | |
namespace Internal { | |
inline ComponentID getUniqueComponentID() | |
{ | |
static ComponentID lastID{0u}; | |
return lastID++; | |
} | |
} | |
template<typename T> inline ComponentID getComponentTypeID() | |
{ | |
static_assert(std::is_base_of<Component, T>::value, | |
"T must inherit from Component"); | |
static ComponentID typeID{Internal::getUniqueComponentID()}; | |
return typeID; | |
} | |
constexpr std::size_t maxComponents{32}; | |
using ComponentBitset = std::bitset<maxComponents>; | |
using ComponentArray = std::array<Component*, maxComponents>; | |
// ...and one for the bitset. | |
constexpr std::size_t maxGroups{32}; | |
using GroupBitset = std::bitset<maxGroups>; | |
struct Component { | |
Entity* entity; | |
virtual void init() {} | |
virtual void update(float deltaTime) {} | |
virtual void draw() {} | |
virtual ~Component() {} | |
}; | |
class Entity { | |
private: | |
// The entity will need a reference to its manager now. | |
Manager& manager; | |
bool alive{true}; | |
std::vector<std::unique_ptr<Component>> components; | |
ComponentArray componentArray; | |
ComponentBitset componentBitset; | |
// Let's add a bitset to our entities. | |
GroupBitset groupBitset; | |
public: | |
Entity(Manager& mManager) : manager(mManager) {} | |
void update(float deltaTime) { for(auto& c : components) c->update(deltaTime); } | |
void draw() { for(auto& c : components) c->draw(); } | |
bool isAlive() const { return alive; } | |
void destroy() { alive = false; } | |
template<typename T> bool hasComponent() const { | |
return componentBitset[getComponentTypeID<T>()]; | |
} | |
// Groups will be handled at runtime, not compile-time: | |
// therefore we will pass groups as a function argument. | |
bool hasGroup(Group mGroup) const | |
{ | |
return groupBitset[mGroup]; | |
} | |
// To add/remove group we define some methods that alter | |
// the bitset and tell the manager what we're doing, | |
// so that the manager can internally store this entity | |
// in its grouped containers. | |
// We'll need to define this method after the definition | |
// of `Manager`, as we're gonna call `Manager::addtoGroup` | |
// here. | |
void addGroup(Group mGroup); | |
void delGroup(Group mGroup) | |
{ | |
groupBitset[mGroup] = false; | |
// We won't notify the manager that a group has been | |
// removed here, as it will automatically remove | |
// entities from the "wrong" group containers during | |
// refresh. | |
} | |
template<typename T, typename... TArgs> | |
T& addComponent(TArgs&&... mArgs) { | |
assert(!hasComponent<T>()); | |
T* c(new T(std::forward<TArgs>(mArgs)...)); | |
c->entity = this; | |
std::unique_ptr<Component> uPtr{c}; | |
components.emplace_back(std::move(uPtr)); | |
componentArray[getComponentTypeID<T>()] = c; | |
componentBitset[getComponentTypeID<T>()] = true; | |
c->init(); | |
return *c; | |
} | |
template<typename T> T& getComponent() const { | |
assert(hasComponent<T>()); | |
auto ptr(componentArray[getComponentTypeID<T>()]); | |
return *reinterpret_cast<T*>(ptr); | |
} | |
}; | |
struct Manager { | |
private: | |
std::vector<std::unique_ptr<Entity>> entities; | |
// We store entities in groups by creating "group buckets" in an | |
// array. `std::vector<Entity*>` could be also replaced for | |
// `std::set<Entity*>`. | |
std::array<std::vector<Entity*>, maxGroups> groupedEntities; | |
public: | |
void update(float deltaTime) { for(auto& e : entities) e->update(deltaTime); } | |
void draw() { for(auto& e : entities) e->draw(); } | |
// When we add a group to an entity, we just add it to the | |
// correct "group bucket". | |
void addToGroup(Entity* mEntity, Group mGroup) { | |
// It would be wise to either assert that the bucket doesn't | |
// already contain `mEntity`, or use a set to prevent duplicates | |
// in exchange for less efficient insertion/iteration. | |
groupedEntities[mGroup].emplace_back(mEntity); | |
} | |
// To get entities that belong to a certain group, we can simply | |
// get one of the "buckets" from the array. | |
std::vector<Entity*>& getEntitiesByGroup(Group mGroup) { | |
return groupedEntities[mGroup]; | |
} | |
void refresh() { | |
// During refresh, we need to remove dead entities and entities | |
// with incorrect groups from the buckets. | |
for(auto i(0u); i < maxGroups; ++i) { | |
auto& v(groupedEntities[i]); | |
v.erase( | |
std::remove_if(std::begin(v), std::end(v), | |
[i](Entity* mEntity) { | |
return !mEntity->isAlive() || !mEntity->hasGroup(i); | |
}), | |
std::end(v)); | |
} | |
entities.erase( | |
std::remove_if(std::begin(entities), std::end(entities), | |
[](const std::unique_ptr<Entity>& mEntity) { | |
return !mEntity->isAlive(); | |
}), | |
std::end(entities)); | |
} | |
Entity& addEntity() { | |
Entity* e(new Entity(*this)); | |
std::unique_ptr<Entity> uPtr{e}; | |
entities.emplace_back(std::move(uPtr)); | |
return *e; | |
} | |
}; | |
// Here's the definition of `Entity::addToGroup`: | |
void Entity::addGroup(Group mGroup) | |
{ | |
groupBitset[mGroup] = true; | |
manager.addToGroup(this, mGroup); | |
} | |
struct CounterComponent : Component { | |
float counter; | |
void init() override { | |
std::cout << "CounterComponent added." << std::endl; | |
} | |
void update(float deltaTime) override { | |
counter += deltaTime; | |
//std::cout << counter << std::endl; | |
} | |
}; | |
struct KillComponent : Component { | |
CounterComponent* cCounter{nullptr}; | |
void init() override { | |
cCounter = &entity->getComponent<CounterComponent>(); | |
std::cout << "KillComponent added." << std::endl; | |
} | |
void update(float deltaTime) override { | |
//if(cCounter->counter >= 100) entity->destroy(); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment