-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hey, so i took a stab at my humble attempt of trying to improve the d…
…ocumentation. I expect this to be an on-going effort, I hope this helps! Let me know if you think this is helpful, I want to help this project succeed! I'm hoping if I pair my "making a game" with "learning to use this library", others will have an *easier* time integrating ecst with their game. Anywho, love to talk this over with you.
- Loading branch information
Showing
3 changed files
with
350 additions
and
259 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
## Build instructions | ||
|
||
1. Acquire and install [`boost::hana`](http://www.boost.org/doc/libs/1_61_0/libs/hana/doc/html/index.html). | ||
2. Clone the repository. | ||
3. Execute the `./init-repository.sh` script. | ||
4. Create a build directory and execute CMake: | ||
|
||
```bash | ||
mkdir ./build | ||
cd ./build | ||
cmake .. | ||
``` | ||
|
||
*(Some examples may require [SFML](https://sfml-dev.org) to be installed.)* |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
```cpp | ||
#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 = ecst::tag::component::v<c::position>; | ||
constexpr auto velocity = ecst::tag::component::v<c::velocity>; | ||
constexpr auto acceleration = ecst::tag::component::v<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 sc = ecst::signature::component; | ||
namespace slc = ecst::signature_list::component; | ||
|
||
// Store `c::acceleration`, `c::velocity` and `c::position` in | ||
// three separate contiguous buffers (SoA). | ||
constexpr auto cs_acceleration = | ||
sc::make(ct::acceleration).contiguous_buffer(); | ||
|
||
constexpr auto cs_velocity = | ||
sc::make(ct::velocity).contiguous_buffer(); | ||
|
||
constexpr auto cs_position = | ||
sc::make(ct::position).contiguous_buffer(); | ||
|
||
// Build and return the "component signature list". | ||
return slc::make(cs_acceleration, cs_velocity, cs_position); | ||
|
||
// Components can be stored in multiple ways, and users | ||
// can define their complex storage types. Here's an example | ||
// of "AoS" storage: | ||
/* | ||
constexpr auto cs_aos_physics = | ||
sc::make(ct::acceleration, ct::velocity, ct::position) | ||
.contiguous_buffer(); | ||
*/ | ||
} | ||
|
||
// 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 split_evenly_per_core = | ||
ips::split_evenly_fn::v_cores(); | ||
|
||
// Acceleration system. | ||
// * Multithreaded. | ||
// * No dependencies. | ||
constexpr auto ssig_acceleration = | ||
ss::make(st::acceleration) | ||
.parallelism(split_evenly_per_core) | ||
.read(ct::acceleration) | ||
.write(ct::velocity); | ||
|
||
// Velocity system. | ||
// * Multithreaded. | ||
constexpr auto ssig_velocity = | ||
ss::make(st::velocity) | ||
.parallelism(split_evenly_per_core) | ||
.dependencies(st::acceleration) | ||
.read(ct::velocity) | ||
.write(ct::position); | ||
|
||
// Build and return the "system signature list". | ||
return sls::make(ssig_acceleration, ssig_velocity); | ||
} | ||
} | ||
|
||
// Create a particle and return its unique 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; | ||
namespace sea = ecst::system_execution_adapter; | ||
|
||
// Define ECST context settings. | ||
constexpr auto s = | ||
ecst::settings::make() | ||
.allow_inner_parallelism() | ||
.fixed_entity_limit(ecst::sz_v<10000>) | ||
.component_signatures(make_csl()) | ||
.system_signatures(make_ssl()) | ||
.scheduler(cs::scheduler<ss::s_atomic_counter>); | ||
|
||
// Create an ECST context. | ||
auto ctx = ecst::context::make_uptr(s); | ||
|
||
// 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) | ||
{ | ||
// Start executing a chain of systems from `st::acceleration`: | ||
proxy.execute_systems_from(st::acceleration)( | ||
// Match systems in the chain by tag... | ||
sea::t(st::acceleration, st::velocity, st::position) | ||
// ...and execute the same logic for every parallel subtask: | ||
.for_subtasks([dt](auto& s, auto& data) | ||
{ | ||
s.process(dt, data); | ||
}) | ||
); | ||
|
||
// Need more control? Here's an additional example: | ||
proxy.execute_systems_from(st::acceleration)( | ||
sea::t(st::acceleration, st::velocity) | ||
.for_subtasks([dt](auto& s, auto& data) | ||
{ | ||
s.process(dt, data); | ||
}), | ||
sea::t(st::position).detailed_instance( | ||
[&proxy, dt](auto& instance, auto& executor) | ||
{ | ||
// Access system `instance` details: | ||
std::cout << instance.subscribed().size() << "\n"; | ||
auto& s(instance.system()); | ||
|
||
do_something_before(); | ||
|
||
executor.for_subtasks([&s, dt](auto& data) | ||
{ | ||
s.process(dt, data); | ||
}); | ||
|
||
do_something_after(); | ||
}) | ||
); | ||
}, | ||
// Refresh events can be caught and handled sequentially. | ||
ecst::refresh_event::on_subscribe(st::acceleration, | ||
[](auto& system, auto eid) | ||
{ | ||
log() << "Entity #" << eid | ||
<< " subscribed to acceleration system.\n". | ||
}), | ||
ecst::refresh_event::on_reclaim([](auto eid) | ||
{ | ||
log() << "Entity #" << eid << " reclaimed.\n". | ||
})); | ||
} | ||
} | ||
``` |
Oops, something went wrong.