Skip to content

Instantly share code, notes, and snippets.

@r-lyeh-archived
Last active December 9, 2019 06:28
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save r-lyeh-archived/7462948 to your computer and use it in GitHub Desktop.
Save r-lyeh-archived/7462948 to your computer and use it in GitHub Desktop.
Component-entity system in 16 lines of C++11. extract from kult engine (https://github.com/r-lyeh/kult)
#include <map> // Component-entity system in 16 lines of C++11. 2013 rlyeh, MIT licensed
#include <set> // Code fragment from kult engine - https://github.com/r-lyeh/kult
enum {JOIN,MERGE,EXCLUDE};using set=std::set<unsigned>;template<typename T> set&system(){
static set entities;return entities;}template<typename T,int MODE>set subsystem(const set
&B){set newset;const set&A=system<T>();if(MODE==MERGE){newset=B;for(auto&id:A)newset.ins\
ert(id);}else if(MODE==EXCLUDE){newset=B;for(auto&id:A)newset.erase(id);}else if(A.size()
<B.size()){for(auto&id:A)if(B.find(id)!=B.end())newset.insert(id);}else{for(auto&id:B)if(
A.find(id)!=A.end())newset.insert(id);}return newset;}template<typename T>std::map<unsig\
ned,T>&components(){static std::map<unsigned,T>objects;return objects;}template<typename\
T>bool has(unsigned id){return components<T>().find(id)!=components<T>().end();}templat\
e<typename T>decltype(T::value_type)&get(unsigned id){static decltype(T::value_type)inva\
lid,reset;return has<T>(id)?components<T>()[id].value_type:invalid=reset;}template<typen\
ame T>decltype(T::value_type)&add(unsigned id){return system<T>().insert(id),components<T
>()[id]=components<T>()[id],get<T>(id);}template<typename T>bool del(unsigned id){return\
add<T>(id),components<T>().erase(id),system<T>().erase(id),!has<T>(id);}template<typena\
me T,int>struct component{T value_type;};
// example:
#include <cassert>
#include <string>
#include <iostream>
// gamedev types and constants
typedef std::pair<int, int> vec2i;
typedef std::pair<float,float> vec2f;
const vec2f zero = { 0.f, 0.f }, one = { 1.f, 1.f };
// component aliases
using friendly = component< bool, 'team' >;
using health = component< int, 'heal' >;
using mana = component< int, 'mana' >;
using coins = component< int, 'coin' >;
using name = component< std::string, 'name' >;
using position = component< vec2f, 'pos2' >;
// sugars
template<class T, class U> std::set< unsigned > join() { return subsystem<T,JOIN>( system<U>() ); }
template<class T, class U, class V> std::set< unsigned > join() { return subsystem<T,JOIN>( join<U,V>() ); }
template<class T, class U, class V, class W> std::set< unsigned > join() { return subsystem<T,JOIN>( join<U,V,W>() ); }
template<class T> std::set< unsigned > exclude( const set &B ) { return subsystem<T,EXCLUDE>(B); }
int main()
{
// entities
int none = 0, player = 1, enemy = 2;
// components
assert( !has<name>(player) );
assert( !has<position>(player) );
assert( !has<coins>(enemy) );
assert( !has<health>(enemy) );
add<name>(player) = "Hero";
add<position>(player) = zero;
add<health>(player) = 100;
add<coins>(player) = 200;
add<mana>(player) = 4000;
add<friendly>(player) = true;
add<name>(enemy) = "Orc";
add<position>(enemy) = one;
add<health>(enemy) = 200;
add<coins>(enemy) = 50;
add<mana>(enemy) = 10;
assert( get<health>(player) == 100 ); // :>
assert( has<name>(player) );
assert( !has<vec2i>(player) );
assert( has<position>(player) );
assert( has<health>(player) );
assert( get<name>(player) == "Hero" );
assert( get<position>(player) == zero );
assert( get<health>(player) == 100 );
// systems; here we intersect a system of all elements with <name> and <position>.
assert( (join<name, position>().size() == 2) );
// systems; render game state
auto display = []() {
std::cout << "- ";
for( auto &id : join<name, coins, health, position>() ) {
std::cout
<< get<name>(id) << " at "
<< "(" << get<position>(id).first << "," << get<position>(id).second << ")"
<< " " << get<health>(id) << "HP"
<< " " << get<coins>(id) << "$, ";
}
std::cout << std::endl;
};
display();
// systems; simulate movement
for( auto &id : join<name, position>() ) {
std::cout << get<name>(id) << " says: im moving!" << std::endl;
vec2f &pos = get<position>(id);
pos.first += 10;
pos.second ++;
}
// systems; simulate a spell bomb in entities of any type
for( auto &id : system<mana>() ) {
std::cout << "spellboomb!!!" << std::endl;
get<mana>(id) -= 50;
}
// systems; simulate a powerup (+$100) for all players
for( auto &id : join<name, coins, friendly>() ) {
get<coins>(id) += 100;
std::cout << get<name>(id) << " says: money! :)" << std::endl;
}
// systems; simulate a poison (-50%HP) to all entities that are not friendly (so enemies)
for( auto &id : exclude<friendly>( join<name, health>() ) ) {
get<health>(id) *= 0.5;
std::cout << get<name>(id) << " says: ugh! poisoned :(" << std::endl;
}
display();
assert( get<health>(player) == 100+0 );
assert( get<health>(enemy) == 200/2 );
assert( get<coins>(player) == 200+100 );
assert( get<coins>(enemy) == 50+0 );
assert( get<mana>(player) == 4000-50 );
assert( get<mana>(enemy) == 10-50 );
assert( del<position>(player) );
assert( !has<position>(player) );
assert( del<name>(player) );
assert( !has<name>(player) );
assert( (join<name, position>().size() == 1) );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment