Experimental & work-in-progress C++14 multithreaded compile-time entity-component-system library.
Slides available on SuperV1234/cppnow2016. (The repository may still be private at the moment.)
// Include "ECST".
#include <ecst.hpp>
// Define some components.
namespace c
{
// Components are simple classes, usually POD structs. There is no need
// for components to derive from a "base component" class or to satisfy
// a particular interface.
struct position
{
vec2f _v;
};
struct velocity
{
vec2f _v;
};
struct acceleration
{
vec2f _v;
};
}
// Define component tags.
namespace ct
{
namespace sc = ecst::signature::component;
constexpr auto position = sc::tag<c::position>;
constexpr auto velocity = sc::tag<c::velocity>;
constexpr auto acceleration = sc::tag<c::acceleration>;
}
// Define some systems.
namespace s
{
// Systems are simple classes as well, that do not need to satisfy any
// particular interface. They can store data and have any method the
// user desires.
// This system accelerates the subscribed particles.
struct acceleration
{
// The `process` method is not hardcoded or specially recognized by
// ECST in any way. Using a lambda in the execution code, we can
// tell an ECST context to execute a particular method (also
// forwarding extra arguments to it).
// The `data` parameter is a proxy object generated by the system
// execution strategy that abstracts away the eventual underlying
// parallelism.
template <typename TData>
void process(ft dt, TData& data)
{
// Notice that the code below does not know anything about the
// multithreading strategy employed by the system: the same
// syntax works with any kind (or lack) of parallel execution.
data.for_entities([&](auto eid)
{
auto& v = data.get(ct::velocity, eid)._v;
const auto& a = data.get(ct::acceleration, eid)._v;
v += a * dt;
});
}
};
// This system moves the subscribed particles.
struct velocity
{
template <typename TData>
void process(ft dt, TData& data)
{
data.for_entities([&](auto eid)
{
auto& p = data.get(ct::position, eid)._v;
const auto& v = data.get(ct::velocity, eid)._v;
p += v * dt;
});
}
};
}
// Setup compile-time settings.
namespace ecst_setup
{
// Builds and returns a "component signature list".
constexpr auto make_csl()
{
namespace slc = ecst::signature_list::component;
return slc::v< // .
c::position, c::velocity, c::acceleration, // .
c::color, c::circle // .
>;
}
// Builds and returns a "system signature list".
constexpr auto make_ssl()
{
// Signature namespace aliases.
namespace ss = ecst::signature::system;
namespace sls = ecst::signature_list::system;
// Inner parallelism aliases and definitions.
namespace ips = ecst::inner_parallelism::strategy;
namespace ipc = ecst::inner_parallelism::composer;
// "Split processing evenly between cores."
constexpr auto par = ips::split_evenly_fn::v_cores();
// Acceleration system.
// * Multithreaded.
// * No dependencies.
constexpr auto ssig_acceleration = // .
ss::make<s::acceleration>( // .
par, // .
ss::no_dependencies, // .
ss::component_use( // .
ss::mutate<c::velocity>, // .
ss::read<c::acceleration> // .
), // .
ss::output::none // .
);
// Velocity system.
// * Multithreaded.
constexpr auto ssig_velocity = // .
ss::make<s::velocity>( // .
par, // .
ss::depends_on<s::acceleration>, // .
ss::component_use( // .
ss::mutate<c::position>, // .
ss::read<c::velocity> // .
), // .
ss::output::none // .
);
// Build and return the "system signature list".
return sls::make( // .
ssig_acceleration, // .
ssig_velocity
);
}
}
// Create a particle, return its ID.
template <typename TProxy>
auto mk_particle(TProxy& proxy, const vec2f& position)
{
auto eid = proxy.create_entity();
auto& ca = proxy.add_component(ct::acceleration, eid);
ca._v.y = 1;
auto& cv = proxy.add_component(ct::velocity, eid);
cv._v = rndvec2f(-3, 3);
return eid;
}
int main()
{
// Namespace aliases.
using namespace ecst_setup;
namespace cs = ecst::settings;
namespace ss = ecst::scheduler;
// Define ECST context settings.
constexpr auto s = ecst::settings::make( // .
cs::multithreaded(cs::allow_inner_parallelism), // .
cs::dynamic, // .
make_csl(), // .
make_ssl(), // .
cs::scheduler<ss::s_atomic_counter> // .
);
// Create an ECST context.
auto ctx_uptr = ecst::context::make_uptr(s);
auto& ctx = *ctx_uptr;
// Initialize context with some entities.
ctx.step([&](auto& proxy)
{
for(sz_t i = 0; i < 1000; ++i)
{
mk_particle(proxy, random_position());
}
});
// "Game loop."
while(true)
{
auto dt = delta_time();
ctx.step([dt](auto& proxy)
{
proxy.execute_systems_overload( // .
[dt](s::acceleration& s, auto& data)
{
s.process(dt, data);
},
[dt](s::velocity& s, auto& data)
{
s.process(dt, data);
});
});
}
}