Skip to content

Instantly share code, notes, and snippets.

@ronchaine
Created July 11, 2016 13:12
Show Gist options
  • Save ronchaine/e74fff9d96c2dd288420682cf26722f4 to your computer and use it in GitHub Desktop.
Save ronchaine/e74fff9d96c2dd288420682cf26722f4 to your computer and use it in GitHub Desktop.
Quick draft for entity-component system.
#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);
}
#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;
}
}
#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_;
}
};
}
#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();
}
};
#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