Created
July 11, 2016 13:12
-
-
Save ronchaine/e74fff9d96c2dd288420682cf26722f4 to your computer and use it in GitHub Desktop.
Quick draft for 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 <vector> | |
#include <unordered_map> | |
#include <set> | |
#include <memory> | |
#include <iostream> | |
#include <type_traits> | |
#include <atomic> | |
#include <cassert> | |
#include <algorithm> | |
#include "ecs.hpp" | |
#include "entity.hpp" | |
#include "rule.hpp" | |
struct Spatial | |
{ | |
float x, y, z; | |
}; | |
struct Frozen | |
{ | |
bool frozen; | |
}; | |
void updatefunc(ecs::Entity& e) | |
{ | |
std::cout << "in updatefunc\n"; | |
e.get<Spatial>().x = 5; | |
} | |
int main() | |
{ | |
ecs::Entity player; | |
ecs::Entity player2; | |
ecs::Rule rule_spatial(ecs::craft_lock<Spatial>()); | |
player.add<Spatial>(); | |
player.add<Frozen>(); | |
// player2.add<Spatial>(); | |
// player.get<Spatial>().x = 0; | |
// player.remove<Spatial>(); | |
rule_spatial.set_update_func(updatefunc); | |
std::vector<ecs::Entity> evec; | |
evec.push_back(player); | |
evec.push_back(player2); | |
rule_spatial.update(evec); | |
rule_spatial.update(player, player2); | |
} |
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
#pragma once | |
#include <cstdint> | |
#include <atomic> | |
#include <iostream> | |
#include <bitset> | |
#include "pool.hpp" | |
namespace ecs | |
{ | |
typedef uint32_t ComponentID; | |
typedef int32_t ComponentIndex; | |
typedef uint64_t Lock; | |
typedef uint64_t Key; | |
constexpr int32_t NO_COMPONENT = -1; | |
constexpr uint32_t MAX_COMPONENTS = 64; | |
namespace internal | |
{ | |
static std::array<BasePool*, MAX_COMPONENTS> componentpool; | |
static std::atomic<ComponentID> IDcounter; | |
} | |
/** | |
* @brief Get type id for the template parameter type | |
* | |
* @return type id | |
*/ | |
template <typename T> | |
ComponentID type() | |
{ | |
static ComponentID id = ++internal::IDcounter; | |
return id; | |
} | |
/** | |
* @brief Adds type to lock | |
* | |
* @param lock_ modified lock | |
*/ | |
template <typename T> | |
void modify_lock(Lock& lock_) | |
{ | |
lock_ |= (1 << ( ecs::type<T>() - 1 )); | |
} | |
/** | |
* @brief Create lock from given template parameters | |
* | |
* @return lock | |
*/ | |
template <typename... Keys> | |
Lock craft_lock() | |
{ | |
Lock rval = 0; | |
int _[] = {0, (modify_lock<Keys>(rval), 0)...}; (void)_; | |
return rval; | |
} | |
/** | |
* @brief Check if a key fits the lock created by template parameters | |
* | |
* @param key Entity key | |
* | |
* @return true if fit, false otherwise | |
*/ | |
template <typename... Keys> | |
bool fits_lock(Key key) | |
{ | |
return ((ecs::craft_lock<Keys...>() & key) == ecs::craft_lock<Keys...>()); | |
} | |
/** | |
* @brief Check if a key fits the lock | |
* | |
* @param lock Rule lock used | |
* @param key Entity key used | |
* | |
* @return true if fit, false otherwise | |
*/ | |
bool fits_lock(Lock lock, Key key) | |
{ | |
return (lock & key) == lock; | |
} | |
/** | |
* @brief Get pointer to a pool of type T | |
* | |
* @return pointer to requested pool type | |
*/ | |
template <typename T> | |
Pool<T>* get_pool() | |
{ | |
ComponentID id = type<T>(); | |
if (internal::componentpool.size() < id) | |
return nullptr; | |
if (internal::componentpool[id] == nullptr) | |
{ | |
// The memory reserved here will still be in use at exit, | |
// should clean up somehow, not technically a leak though. | |
Pool<T>* pool = new Pool<T>(); | |
internal::componentpool[id] = pool; | |
return pool; | |
} | |
return static_cast<Pool<T>*> (internal::componentpool[id]); | |
} | |
/** | |
* @brief Searches component pool for the next empty spot and returns its index. | |
* | |
* @return Index to a free component or -1 in case of error | |
*/ | |
template <typename T> | |
ComponentIndex get_free() | |
{ | |
Pool<T>& pool = *(Pool<T>*)internal::componentpool[type<T>()]; | |
ComponentIndex idx = pool.find_empty(); | |
if (idx < 0) | |
return NO_COMPONENT; | |
pool.set_used(idx); | |
return idx; | |
} | |
} |
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
#pragma once | |
#include <unordered_map> | |
#include "ecs.hpp" | |
namespace ecs | |
{ | |
/** | |
* @brief Entity class, holds components and does little else | |
*/ | |
class Entity | |
{ | |
protected: | |
std::unordered_map<uint32_t, ecs::ComponentIndex> componentmap; | |
Key key_; | |
public: | |
Entity() : key_(0) {} | |
/** | |
* @brief Checks if entity has component given by template parameter | |
* | |
* @return true or false | |
*/ | |
template <typename T> | |
bool has_component() | |
{ | |
if (ecs::get_pool<T>() == nullptr) | |
return false; | |
if (!(componentmap.count(ecs::type<T>()))) | |
return false; | |
if (componentmap[ecs::type<T>()] == ecs::NO_COMPONENT) | |
return false; | |
return true; | |
} | |
/** | |
* @brief Add component to the entity | |
* | |
* @return true if successful, otherwise false | |
*/ | |
template <typename T> | |
bool add() | |
{ | |
if (has_component<T>()) | |
return true; | |
if (ecs::get_pool<T>() == nullptr) | |
return false; | |
ecs::ComponentIndex idx = ecs::get_free<T>(); | |
if (idx == ecs::NO_COMPONENT) | |
{ | |
std::cout << "couldn't get free component index\n"; | |
return false; | |
} | |
key_ |= (1 << ( ecs::type<T>() - 1 )); | |
componentmap[ecs::type<T>()] = idx; | |
return true; | |
} | |
/** | |
* @brief Get component struct used by entity | |
* | |
* @return reference to requested struct | |
*/ | |
template <typename T> | |
T& get() | |
{ | |
assert(has_component<T>()); | |
Pool<T>* pool = ecs::get_pool<T>(); | |
ecs::ComponentIndex idx = componentmap[ecs::type<T>()]; | |
return pool->at(componentmap[idx]); | |
} | |
/** | |
* @brief Remove component from entity | |
*/ | |
template <typename T> | |
void remove() | |
{ | |
assert(has_component<T>()); | |
Pool<T>* pool = ecs::get_pool<T>(); | |
pool->unset_used(componentmap[ecs::type<T>()]); | |
key_ &= ~((1 << ( ecs::type<T>() - 1 ))); | |
componentmap.erase(ecs::type<T>()); | |
} | |
/** | |
* @brief Get entity key | |
* | |
* @return key | |
*/ | |
Key key() | |
{ | |
return key_; | |
} | |
}; | |
} |
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
#pragma once | |
#include <cstdint> | |
#include <vector> | |
#include <set> | |
#include <memory> | |
#include <iostream> | |
struct BasePool {}; | |
/** | |
* @brief Sequential container that reuses forgotten elements | |
*/ | |
template <typename T> | |
class Pool : public BasePool | |
{ | |
friend class iterator; | |
friend class const_iterator; | |
private: | |
std::set<size_t> used_indices; | |
std::vector<T> data; | |
public: | |
class iterator | |
{ | |
Pool<T>& pool_; | |
size_t ptr_; | |
public: | |
typedef iterator self_type; | |
typedef T value_type; | |
typedef T& reference; | |
typedef T* pointer; | |
typedef std::forward_iterator_tag iterator_category; | |
typedef int difference_type; | |
iterator(Pool<T> pool) : pool_(pool) {} | |
iterator(Pool<T> pool, size_t index) : pool_(pool), ptr_(index) {} | |
self_type operator++() // prefix | |
{ | |
ptr_++; | |
return *this; | |
} | |
self_type operator++(int) // postfix | |
{ | |
self_type i = *this; | |
ptr_++; | |
return i; | |
} | |
reference operator*() { | |
T& ref = pool_.data[ptr_]; | |
return ref; | |
} | |
pointer operator->() { return &data[ptr_]; } | |
bool operator==(const self_type& rhs) { return ptr_ == rhs.ptr_; } | |
bool operator!=(const self_type& rhs) { return ptr_ != rhs.ptr_; } | |
}; | |
class const_iterator | |
{ | |
}; | |
iterator begin() | |
{ | |
iterator it(*this, 0); | |
return it; | |
} | |
iterator end() | |
{ | |
iterator it(*this, data.size()); | |
return it; | |
} | |
int32_t find_empty() | |
{ | |
for (size_t i = 0; i < data.size(); ++i) | |
{ | |
// Not in used indices, we can use this | |
if (used_indices.count(i) == 0) | |
return i; | |
} | |
// no unused indices, we need a new one | |
data.resize(data.size() + 1); | |
return data.size() -1; | |
} | |
void set_used(size_t index) | |
{ | |
used_indices.insert(index); | |
} | |
void unset_used(size_t index) | |
{ | |
used_indices.erase(index); | |
} | |
T& at(const size_t index) | |
{ | |
return data[index]; | |
} | |
~Pool() | |
{ | |
while(data.size()) | |
data.pop_back(); | |
} | |
}; |
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
#pragma once | |
#include <type_traits> | |
#include <algorithm> | |
#include "entity.hpp" | |
#include "ecs.hpp" | |
namespace ecs | |
{ | |
/** | |
* @brief Type for any container that holds Entity types | |
*/ | |
template <typename T> | |
concept bool EntityContainer = std::is_same<typename T::value_type, Entity>::value && requires (T a) | |
{ | |
{ | |
a.begin(), | |
a.end() | |
} | |
}; | |
/** | |
* @brief Rule (system) for handling entities | |
*/ | |
class Rule | |
{ | |
private: | |
Lock lock_; | |
std::function<void(Entity&)> func; | |
public: | |
Rule(Lock lock) : lock_(lock), func(nullptr) {} | |
Rule(Lock lock, std::function<void(Entity&)> f) : lock_(lock), func(f) {} | |
/** | |
* @brief Perform checks and call the update function for entity | |
* | |
* @param ent entity to update | |
*/ | |
inline void update(Entity& ent) | |
{ | |
if (!ecs::fits_lock(lock_, ent.key())) | |
return; | |
if (func == nullptr) | |
{ | |
std::cout << "cannot update -- no update function specified\n"; | |
return; | |
} | |
func(ent); | |
} | |
/** | |
* @brief Update entities in a container | |
* | |
* @param entities any stl-style container holding type Entity | |
*/ | |
template <EntityContainer T> // Container T with c++ concepts | |
void update(T entities) | |
{ | |
std::for_each(entities.begin(), entities.end(), [this](Entity& e) { update(e); } ); | |
} | |
/** | |
* @brief Update entities specified by variadic list | |
*/ | |
template <typename... Rest> | |
void update(Entity ent, Rest... rest) | |
{ | |
update(ent); | |
update(rest...); | |
} | |
/** | |
* @brief Set function to call when updating | |
* | |
* @param Anything that fits std::function | |
*/ | |
void set_update_func(std::function<void(Entity&)> f) | |
{ | |
func = f; | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment