aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlec Thomas <alec@swapoff.org>2012-10-20 20:25:04 -0400
committerAlec Thomas <alec@swapoff.org>2012-10-20 20:56:20 -0400
commitda091e7c6fbe409ab704187f6ada9db1f2e0dece (patch)
tree4c2bc11205b60433859403c1e3dbf2b9d1dad1bf
parentff28ad76a728397d3894568a67c44156307bdca0 (diff)
Implemented unified entity iteration and component unpacking.
Continued working on docs.
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md85
-rw-r--r--entityx/Entity.h93
-rw-r--r--entityx/Entity_test.cc71
-rw-r--r--entityx/System.h7
-rw-r--r--entityx/World.cc1
6 files changed, 199 insertions, 60 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e34eec3..253cc70 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -40,6 +40,8 @@ require(HAS_CXX11_RVALUE_REFERENCES "C++11 rvalue reference support")
#require(HAS_CXX11_CSTDINT_H "C++11 stdint support")
require(HAS_CXX11_VARIADIC_TEMPLATES "C++11 variadic templates")
require(HAS_CXX11_RVALUE_REFERENCES "C++11 rvalue references")
+require(HAS_CXX11_LONG_LONG "C++11 long long")
+require(HAS_CXX11_LONG_LONG "C++11 lambdas")
message("-- Checking misc features")
require(HAVE_STDINT_H "stdint.h")
diff --git a/README.md b/README.md
index 82cf6d9..f0fbe4e 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,27 @@ EntityX is an EC system that uses C++11 features to provide type-safe component
## Overview
-In EntityX, data is associated with entities through components. This data is then used by systems to implement behavior. Behavior systems can utilize as many types of data as necessary. As an example, a physics system might need *position* and *mass* data, while a collision system might only need *position* - the data would be logically separated, but usable by any system.
+In EntityX, data is associated with entities via components. Systems then use component data to implement behavior. Systems can utilize as many components as necessary. As an example, a physics system might need *position* and *mass* data, while a collision system might only need *position* - the data would be logically separated, but usable by any system.
-Finally, an event system ties behavior systems together, allowing them to interact without tight coupling.
+Finally, an event system ties systems together, allowing them to interact without being tightly coupled.
### Entities
-Entities are simply 64-bit numeric identifiers with which behaviors are associated. Entity IDs are allocated by the `EntityManager`, and all entities assigned particular types of data can be retrieved.
+Entities are simply 64-bit numeric identifiers with which component data is associated. Entity IDs are allocated by the `EntityManager`. Data can then be associated with an entity, and queried or retrieved directly.
+
+Creating an entity is as simple as:
+
+```
+EntityManager entities;
+
+Entity entity = entities.create();
+```
### Components (entity data)
-Components are typically POD types containing self-contained sets of related data. Implementations are [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) subclasses of `Component<T>`.
+Components are typically POD types containing self-contained sets of related data. Implementations are [curiously recurring template pattern](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) (CRTP) subclasses of `Component<T>`.
+
+#### Creating components
As an example, position and direction information might be represented as:
@@ -32,26 +42,57 @@ struct Direction : Component<Direction> {
float x, y;
};
+```
+
+#### Assigning components to entities
+
+To associate a component with a previously created entity call ``EntityManager::assign<C>()`` with the component type, the entity, and any component constructor arguments:
+
+```
+// Assign a Position with x=1.0f and y=2.0f to "entity"
+entities.assign<Position>(entity, 1.0f, 2.0f);
+```
+
+You can also assign existing instances of components:
+
+```
+boost::shared_ptr<Position> position = boost::make_shared<Position>(1.0f, 2.0f);
+entities.assign(entity, position);
+```
+
+#### Querying entities and components
+
+To retrieve a component associated with an entity use ``EntityManager::component()``:
+
+```
+boost::shared_ptr<Position> position = entities.component<Position>();
+if (position) {
+ // Do stuff with position
+}
+```
+To query all components with a set of components assigned use ``EntityManager::entities_with_components()``. This method will return only those entities that have *all* of the specified components associated with them, assigning the component pointer to the corresponding component pointer:
+
+```
+Position *position;
+Direction *direction;
+for (auto entity : entities.entities_with_components(position, direction)) {
+ // Do things with entity ID, position and direction.
+}
```
### Systems (implementing behavior)
-Systems implement behavior using one or more components. Implementations are [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) subclasses of `System<T>`.
+Systems implement behavior using one or more components. Implementations are [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) subclasses of `System<T>` and *must* implement the `update()` method, as shown below.
A basic movement system might be implemented with something like the following:
```
-class MovementSystem : public System<MovementSystem> {
- public:
- MovementSystem() {}
-
- void update(EntityManager &es, EventManager &events, double) override {
- auto entities = es.entities_with_components<Position, Direction>();
+struct MovementSystem : public System<MovementSystem> {
+ void update(EntityManager &es, EventManager &events, double dt) override {
Position *position;
Direction *direction;
- for (auto entity : entities) {
- es.unpack<Position, Direction>(entity, position, direction);
+ for (auto entity : es.entities_with_components(position, direction)) {
position->x += direction->x;
position->y += direction->y;
}
@@ -80,26 +121,16 @@ Next we implement our collision system, which emits ``Collision`` objects via an
```
class CollisionSystem : public System<CollisionSystem> {
public:
- CollisionSystem(EventManager &events) : events_(events) {}
-
void update(EntityManager &es, EventManager &events, double dt) override {
Position *left_position, *right_position;
- auto left_entities = es.entities_with_components<Position>(),
- right_entities = es.entities_with_components<Position>();
-
- for (auto left_entity : left_entities) {
- es.unpack<Position>(left_entity, left_position);
- for (auto right_entity : right_entities) {
- es.unpack<Position>(right_entity, right_position);
+ for (auto left_entity : es.entities_with_components(left_position)) {
+ for (auto right_entity : es.entities_with_components(right_position)) {
if (collide(left_position, right_position)) {
- events_.emit<Collision>(left_entity, right_entity);
+ events.emit<Collision>(left_entity, right_entity);
}
}
}
}
-
- private:
- EventManager &events_;
};
```
@@ -125,6 +156,8 @@ DebugCollisions debug_collisions;
events.subscribe<Collision>(debug_collisions);
```
+### World (tying it all together)
+
## Installation
EntityX has the following build and runtime requirements:
diff --git a/entityx/Entity.h b/entityx/Entity.h
index e7763bb..85ee2b7 100644
--- a/entityx/Entity.h
+++ b/entityx/Entity.h
@@ -71,19 +71,6 @@ struct BaseComponent {
template <typename Derived>
struct Component : public BaseComponent {
public:
- /**
- * Emitted when a component of this type is added to an entity.
- */
- class AddEvent : public Event<AddEvent> {
- public:
- AddEvent(Entity entity, boost::shared_ptr<Derived> component) :
- entity(entity), component(component) {}
-
- Entity entity;
- boost::shared_ptr<Derived> component;
- };
-
-
/// Used internally for registration.
static Family family() {
static Family family = family_counter_++;
@@ -118,13 +105,14 @@ struct EntityDestroyedEvent : public Event<EntityDestroyedEvent> {
/**
* Emitted when any component is added to an entity.
*/
-struct ComponentAddedEvent : public Event<ComponentAddedEvent> {
- ComponentAddedEvent(EntityManager &manager, Entity entity, boost::shared_ptr<BaseComponent> component) :
+template <typename T>
+struct ComponentAddedEvent : public Event<ComponentAddedEvent<T>> {
+ ComponentAddedEvent(EntityManager &manager, Entity entity, boost::shared_ptr<T> component) :
manager(manager), entity(entity), component(component) {}
EntityManager &manager;
Entity entity;
- boost::shared_ptr<BaseComponent> component;
+ boost::shared_ptr<T> component;
};
@@ -148,8 +136,7 @@ class EntityManager : boost::noncopyable {
public:
EntityManager(EventManager &event_manager) : event_manager_(event_manager) {}
- class View {
- private:
+ class View {
public:
typedef boost::function<bool (EntityManager &, Entity)> Predicate;
@@ -183,7 +170,9 @@ class EntityManager : boost::noncopyable {
private:
friend class View;
- Iterator(EntityManager &manager, const std::vector<Predicate> &predicates, Entity entity) : manager_(manager), predicates_(predicates), i_(entity) {
+ Iterator(EntityManager &manager, const std::vector<Predicate> &predicates,
+ const std::vector<boost::function<void (Entity)>> &unpackers, Entity entity)
+ : manager_(manager), predicates_(predicates), unpackers_(unpackers), i_(entity) {
next();
}
@@ -191,6 +180,15 @@ class EntityManager : boost::noncopyable {
while (i_ < manager_.size() && !predicate()) {
++i_;
}
+ if (i_ < manager_.size()) {
+ unpack();
+ }
+ }
+
+ void unpack() {
+ for (auto unpacker : unpackers_) {
+ unpacker(i_);
+ }
}
bool predicate() {
@@ -204,6 +202,7 @@ class EntityManager : boost::noncopyable {
EntityManager &manager_;
const std::vector<Predicate> predicates_;
+ std::vector<boost::function<void (Entity)>> unpackers_;
Entity i_;
};
@@ -212,11 +211,25 @@ class EntityManager : boost::noncopyable {
predicates_.push_back(predicate);
}
- Iterator begin() { return Iterator(manager_, predicates_, 0); }
- Iterator end() { return Iterator(manager_, predicates_, manager_.size()); }
- const Iterator begin() const { return Iterator(manager_, predicates_, 0); }
- const Iterator end() const { return Iterator(manager_, predicates_, manager_.size()); }
+ Iterator begin() { return Iterator(manager_, predicates_, unpackers_, 0); }
+ Iterator end() { return Iterator(manager_, predicates_, unpackers_, manager_.size()); }
+ const Iterator begin() const { return Iterator(manager_, predicates_, unpackers_, 0); }
+ const Iterator end() const { return Iterator(manager_, predicates_, unpackers_, manager_.size()); }
+
+ // It's a bit less than ideal to mix this int othe View, but I couldn't find a way to separate concerns without
+ // vastly increasing the amount of code that had to be written.
+ template <typename A>
+ void unpack_to(A *&a) {
+ unpackers_.push_back([&] (Entity id) {
+ a = manager_.component<A>(id).get();
+ });
+ }
+ template <typename A, typename B, typename ... Args>
+ void unpack_to(A *&a, B *&b, Args *& ... args) {
+ unpack_to<A>(a);
+ unpack_to<B, Args ...>(b, args ...);
+ }
private:
friend class EntityManager;
@@ -226,6 +239,7 @@ class EntityManager : boost::noncopyable {
EntityManager &manager_;
std::vector<Predicate> predicates_;
+ std::vector<boost::function<void (Entity)>> unpackers_;
};
/**
@@ -287,9 +301,7 @@ class EntityManager : boost::noncopyable {
entity_components_.at(C::family()).at(entity) = base;
entity_component_mask_.at(entity) |= uint64_t(1) << C::family();
- // TODO(alec): Figure out why this doesn't compile...gets an odd error about AddEvent not being a value.
- //event_manager_.emit<C::AddEvent>(entity, component);
- event_manager_.emit<ComponentAddedEvent>(*this, entity, base);
+ event_manager_.emit<ComponentAddedEvent<C>>(*this, entity, component);
return component;
}
@@ -339,6 +351,28 @@ class EntityManager : boost::noncopyable {
}
/**
+ * Get all entities with the given component.
+ */
+ template <typename C>
+ View entities_with_components(C *&c) {
+ auto mask = component_mask<C>();
+ auto view = View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask));
+ view.unpack_to<C>(c);
+ return view;
+ }
+
+ /**
+ * Find Entities that have all of the specified Components.
+ */
+ template <typename C1, typename C2, typename ... Components>
+ View entities_with_components(C1 *&c1, C2 *&c2, Components *& ... args) {
+ auto mask = component_mask<C1, C2, Components ...>();
+ auto view = View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask));
+ view.unpack_to<C1, C2, Components...>(c1, c2, args...);
+ return view;
+ }
+
+ /**
* Unpack components directly into pointers.
*
* Components missing from the entity will be set to nullptr.
@@ -348,11 +382,6 @@ class EntityManager : boost::noncopyable {
* Position *p;
* Direction *d;
* unpack<Position, Direction>(e, p, d);
- *
- * Ideally this process would be more like:
- *
- * for (auto components : em.unpack_entities<Position, Direction>()) {
- * }
*/
template <typename A>
void unpack(Entity id, A *&a) {
@@ -372,7 +401,7 @@ class EntityManager : boost::noncopyable {
*/
template <typename A, typename B, typename ... Args>
void unpack(Entity id, A *&a, B *&b, Args *& ... args) {
- a = component<A>(id).get();
+ unpack<A>(id, a);
unpack<B, Args ...>(id, b, args ...);
}
diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc
index 8feb91a..962d858 100644
--- a/entityx/Entity_test.cc
+++ b/entityx/Entity_test.cc
@@ -33,16 +33,33 @@ int size(const T &t) {
struct Position : Component<Position> {
Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+ bool operator == (const Position &other) const { return x == other.x && y == other.y; }
+
float x, y;
};
+
+ostream &operator << (ostream &out, const Position &position) {
+ out << "Position(" << position.x << ", " << position.y << ")";
+ return out;
+}
+
+
struct Direction : Component<Direction> {
Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+ bool operator == (const Direction &other) const { return x == other.x && y == other.y; }
+
float x, y;
};
+ostream &operator << (ostream &out, const Direction &direction) {
+ out << "Direction(" << direction.x << ", " << direction.y << ")";
+ return out;
+}
+
+
class EntityManagerTest : public ::testing::Test {
protected:
EntityManagerTest() : em(ev) {}
@@ -133,6 +150,34 @@ TEST_F(EntityManagerTest, TestGetEntitiesWithIntersectionOfComponents) {
ASSERT_EQ(25, size(em.entities_with_components<Direction, Position>()));
}
+TEST_F(EntityManagerTest, TestGetEntitiesWithComponentAndUnpacking) {
+ vector<Entity> entities;
+ Entity e = em.create();
+ Entity f = em.create();
+ Entity g = em.create();
+ std::vector<std::pair<boost::shared_ptr<Position>, boost::shared_ptr<Direction>>> position_directions;
+ position_directions.push_back(std::make_pair(
+ em.assign<Position>(e, 1.0f, 2.0f),
+ em.assign<Direction>(e, 3.0f, 4.0f)));
+ em.assign<Position>(f, 5.0f, 6.0f);
+ position_directions.push_back(std::make_pair(
+ em.assign<Position>(g, 7.0f, 8.0f),
+ em.assign<Direction>(g, 9.0f, 10.0f)));
+ Position *position;
+ Direction *direction;
+ int i = 0;
+ for (auto unused_entity : em.entities_with_components(position, direction)) {
+ (void)unused_entity;
+ ASSERT_TRUE(position != nullptr);
+ ASSERT_TRUE(direction != nullptr);
+ auto pd = position_directions.at(i);
+ ASSERT_EQ(*position, *pd.first);
+ ASSERT_EQ(*direction, *pd.second);
+ ++i;
+ }
+ ASSERT_EQ(2, i);
+}
+
TEST_F(EntityManagerTest, TestUnpack) {
Entity e = em.create();
auto p = em.assign<Position>(e);
@@ -161,7 +206,6 @@ TEST_F(EntityManagerTest, TestComponentIdsDiffer) {
ASSERT_NE(Position::family(), Direction::family());
}
-
TEST_F(EntityManagerTest, TestEntityCreatedEvent) {
struct EntityCreatedEventReceiver : public Receiver<EntityCreatedEventReceiver> {
void receive(const EntityCreatedEvent &event) {
@@ -181,7 +225,6 @@ TEST_F(EntityManagerTest, TestEntityCreatedEvent) {
ASSERT_EQ(10, receiver.created.size());
};
-
TEST_F(EntityManagerTest, TestEntityDestroyedEvent) {
struct EntityDestroyedEventReceiver : public Receiver<EntityDestroyedEventReceiver> {
void receive(const EntityDestroyedEvent &event) {
@@ -205,3 +248,27 @@ TEST_F(EntityManagerTest, TestEntityDestroyedEvent) {
}
ASSERT_TRUE(entities == receiver.destroyed);
};
+
+TEST_F(EntityManagerTest, TestComponentAddedEvent) {
+ struct ComponentAddedEventReceiver : public Receiver<ComponentAddedEventReceiver> {
+ void receive(const ComponentAddedEvent<Position> &event) {
+ auto p = event.component;
+ float n = float(created.size());
+ ASSERT_EQ(p->x, n);
+ ASSERT_EQ(p->y, n);
+ created.push_back(event.entity);
+ }
+
+ vector<Entity> created;
+ };
+
+ ComponentAddedEventReceiver receiver;
+ ev.subscribe<ComponentAddedEvent<Position>>(receiver);
+
+ ASSERT_EQ(0, receiver.created.size());
+ for (int i = 0; i < 10; ++i) {
+ Entity e = em.create();
+ em.assign<Position>(e, float(i), float(i));
+ }
+ ASSERT_EQ(10, receiver.created.size());
+};
diff --git a/entityx/System.h b/entityx/System.h
index c2cead9..1881b13 100644
--- a/entityx/System.h
+++ b/entityx/System.h
@@ -92,6 +92,13 @@ class SystemManager : boost::noncopyable {
return s;
}
+ /**
+ * Retrieve the registered System instance, if any.
+ *
+ * boost::shared_ptr<CollisionSystem> collisions = systems.system<CollisionSystem>();
+ *
+ * @return System instance or empty shared_ptr<S>.
+ */
template <typename S>
boost::shared_ptr<S> system() {
auto it = systems_.find(S::family());
diff --git a/entityx/World.cc b/entityx/World.cc
index 5e8d8db..05a10d7 100644
--- a/entityx/World.cc
+++ b/entityx/World.cc
@@ -21,6 +21,7 @@ void World::start() {
void World::run() {
running_ = true;
double dt;
+ timer_.restart();
while (running_) {
dt = timer_.elapsed();
timer_.restart();