public
Last active

entity/component system tests

  • Download Gist
ecstests.hpp
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
#include <stack>
#include <vector>
#include <tuple>
#include <numeric>
#include <cstdint>
#include <array>
#include <cassert>
#include <unordered_map>
#include <bitset>
#include <SSVUtils/SSVUtils.h>
 
using Id = std::size_t;
using Ctr = std::uint8_t;
using TypeId = std::size_t;
static constexpr std::size_t maxEntities{1000000};
static constexpr std::size_t maxComponentsPerEntity{64};
using Bitset = std::bitset<maxComponentsPerEntity>;
 
class Manager;
class EntityHandle;
class Component { };
 
namespace Internal
{
// Last used bit index
static unsigned int lastIdx{0};
 
template<typename T> struct TypeIdStorage
{
// TypeIdStorage statically stores the TypeId and bit index of a Component type
 
static_assert(std::is_base_of<Component, T>::value, "TypeIdStorage only works with types that derive from Component");
 
static const TypeId typeId;
static const std::size_t idx;
};
template<typename T> const TypeId TypeIdStorage<T>::typeId{typeid(T).hash_code()};
template<typename T> const std::size_t TypeIdStorage<T>::idx{lastIdx++};
}
 
// Shortcut to get the static TypeId of a Component type from TypeIdStorage
template<typename T> inline constexpr const TypeId& getTypeId() { return Internal::TypeIdStorage<T>::typeId; }
 
// These functions use variadic template recursion to "build" a bitset for a set of Component types
template<typename T> inline static void buildBitsetHelper(Bitset& mBitset) noexcept { mBitset.set(Internal::TypeIdStorage<T>::idx); }
template<typename T1, typename T2, typename... TArgs> inline static void buildBitsetHelper(Bitset& mBitset) noexcept { buildBitsetHelper<T1>(mBitset); buildBitsetHelper<T2, TArgs...>(mBitset); }
template<typename... TArgs> inline static Bitset getBitset() noexcept { Bitset result; buildBitsetHelper<TArgs...>(result); return result; }
 
class IdPool
{
// IdPool stores available Entity ids and is used to check Entity validity
 
private:
std::vector<Id> available;
std::array<Ctr, maxEntities> counters;
 
public:
inline IdPool() : available(maxEntities)
{
// Fill the available ids vector from 0 to maxEntities
std::iota(std::begin(available), std::end(available), 0);
}
 
// Returns the first unused id
inline Id getAvailableId() noexcept { Id result(available.back()); available.pop_back(); return result; }
 
// Used on Entity death, reclaims the Entity's id so that it can be reused
inline void reclaim(const Id& mId) noexcept { ++counters[mId]; available.emplace_back(mId); }
 
// Checks if an Entity is currently alive
inline bool isAlive(const Id& mId, const Ctr& mCtr) const noexcept { return counters[mId] == mCtr; }
};
 
class Entity
{
friend class EntityHandle;
 
private:
Manager& manager;
std::unordered_map<TypeId, ssvu::Uptr<Component>> components;
Bitset typeIdBitset;
 
public:
inline Entity(Manager& mManager) noexcept : manager(mManager) { }
 
template<typename T, typename... TArgs> inline void createComponent(TArgs&&... mArgs)
{
assert(!hasComponent<T>());
components[getTypeId<T>()] = ssvu::make_unique<T>(std::forward<TArgs>(mArgs)...);
buildBitsetHelper<T>(typeIdBitset);
}
template<typename T> inline bool hasComponent() const noexcept { return components.count(getTypeId<T>()) > 0; }
template<typename T> inline T& getComponent() { assert(hasComponent<T>()); return *reinterpret_cast<T*>(components[getTypeId<T>()].get()); }
 
inline Manager& getManager() noexcept { return manager; }
inline const Bitset& getTypeIdBitset() const noexcept { return typeIdBitset; }
};
 
template<typename T> constexpr inline static std::tuple<T&> buildComponentsTuple(Entity& mEntity) { return std::tuple<T&>{mEntity.getComponent<T>()}; }
template<typename T1, typename T2, typename... TArgs> constexpr inline static std::tuple<T1&, T2&, TArgs&...> buildComponentsTuple(Entity& mEntity) { return std::tuple_cat(buildComponentsTuple<T1>(mEntity), buildComponentsTuple<T2, TArgs...>(mEntity)); }
 
struct SystemBase
{
private:
Bitset typeIdBitset;
 
protected:
inline SystemBase(Bitset mTypeIdBitset) : typeIdBitset{std::move(mTypeIdBitset)} { }
 
public:
inline virtual void update() = 0;
inline virtual void registerEntity(Entity&) = 0;
inline const Bitset& getTypeIdBitset() const noexcept { return typeIdBitset; }
};
 
template<typename... TArgs> class System : public SystemBase
{
private:
std::vector<std::tuple<Entity&, TArgs&...>> tuples;
 
public:
ssvu::Func<void(Entity&, TArgs&...)> onUpdate;
 
inline System() noexcept : SystemBase{getBitset<TArgs...>()} { }
 
inline void registerEntity(Entity& mEntity) override { tuples.push_back(std::tuple_cat(std::tuple<Entity&>{mEntity}, buildComponentsTuple<TArgs...>(mEntity))); }
inline void update() override { for(auto& t : tuples) ssvu::explode(onUpdate, t); }
};
 
class EntityHandle
{
private:
Manager& manager;
IdPool& idPool;
Entity& entity;
Id id;
Ctr counter;
 
public:
inline EntityHandle(Manager& mManager, IdPool& mIdPool, Entity& mEntity, const Id& mId) noexcept : manager(mManager), idPool(mIdPool), entity(mEntity), id{mId} { }
 
template<typename T, typename... TArgs> inline void createComponent(TArgs&&... mArgs) { assert(isAlive()); entity.createComponent<T>(std::forward<TArgs>(mArgs)...); }
template<typename T> inline bool hasComponent() const noexcept { assert(isAlive()); return entity.hasComponent<T>(); }
template<typename T> inline T& getComponent() { assert(isAlive()); return entity.getComponent<T>(); }
 
inline bool isAlive() const noexcept { return idPool.isAlive(id, counter); }
inline Manager& getManager() noexcept { return manager; }
inline Entity& getEntity() noexcept { return entity; }
inline const Id& getId() const noexcept { return id; }
inline const Ctr& getCtr() const noexcept { return counter; }
};
 
class Manager
{
private:
std::vector<Entity*> entitiesToAdd, entitiesToRemove;
 
IdPool entityIdPool;
std::vector<ssvu::Uptr<Entity>> entities;
std::vector<ssvu::Uptr<SystemBase>> systems;
 
public:
inline void refresh()
{
for(auto& e : entitiesToAdd)
{
for(auto& s : systems) if((e->getTypeIdBitset() & s->getTypeIdBitset()) == s->getTypeIdBitset()) s->registerEntity(*e);
entities.emplace_back(e);
}
 
entitiesToAdd.clear();
}
inline void update() { for(auto& s : systems) s->update(); }
 
inline EntityHandle createEntity()
{
auto entity(new Entity{*this});
entitiesToAdd.push_back(entity);
return {*this, entityIdPool, *entity, entityIdPool.getAvailableId()};
}
 
template<typename T, typename... TArgs> void registerSystem(TArgs&&... mArgs)
{
auto system(new T{std::forward<TArgs>(mArgs)...});
systems.emplace_back(system);
}
 
inline IdPool& getEntityIdPool() noexcept { return entityIdPool; }
};
 
// ---
 
#define SYSTEM_UPDATE(system, body) inline system() { onUpdate = []body ;}
 
struct CPosition : Component { int x, y; CPosition(int mX, int mY) : x{mX}, y{mY} { } };
struct CVelocity : Component { int x, y; CVelocity(int mX, int mY) : x{mX}, y{mY} { } };
struct CAcceleration : Component { int x, y; CAcceleration(int mX, int mY) : x{mX}, y{mY} { } };
 
struct SMovement : System<CPosition, CVelocity>
{
SYSTEM_UPDATE(SMovement, (Entity& mEntity, CPosition& mCPosition, CVelocity& mCVelocity)
{
mCPosition.x += mCVelocity.x;
mCPosition.y += mCVelocity.y;
ssvu::lo << "sm:" << mCPosition.x << " ; " << mCPosition.y << std::endl;
})
};
 
struct SMovementAdv : System<CVelocity, CAcceleration>
{
inline SMovementAdv()
{
onUpdate = [](Entity& mEntity, CVelocity& mCVelocity, CAcceleration& mCAcceleration)
{
mCVelocity.x += mCAcceleration.x;
mCVelocity.y += mCAcceleration.y;
ssvu::lo << "smadv:" << mCVelocity.x << " ; " << mCVelocity.y << std::endl;
};
}
};
 
 
int main()
{
using namespace std;
using namespace ssvu;
 
 
Manager manager;
manager.registerSystem<SMovement>();
manager.registerSystem<SMovementAdv>();
 
{
auto e = manager.createEntity();
e.createComponent<CPosition>(0, 0);
e.createComponent<CVelocity>(10, 5);
e.createComponent<CAcceleration>(-3, 5);
}
 
{
auto e = manager.createEntity();
e.createComponent<CPosition>(0, 0);
e.createComponent<CAcceleration>(-3, 5);
}
 
manager.refresh();
manager.update();
manager.update();
manager.update();
 
 
/*TestSystem ts;
Manager m;
auto handle = m.createEntity();
handle.createComponent<TestComponent>();*/
 
return 0;
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.