]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Implemented unified entity iteration and component unpacking.
authorAlec Thomas <alec@swapoff.org>
Sun, 21 Oct 2012 00:25:04 +0000 (20:25 -0400)
committerAlec Thomas <alec@swapoff.org>
Sun, 21 Oct 2012 00:56:20 +0000 (20:56 -0400)
Continued working on docs.

CMakeLists.txt
README.md
entityx/Entity.h
entityx/Entity_test.cc
entityx/System.h
entityx/World.cc

index e34eec318896bb4bb731770cca6311e97bc2dd33..253cc709eddf087fada2adb18d4fb257b3bfbbf5 100644 (file)
@@ -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")
index 82cf6d9883a3fc7ea1c56f9eda986c0d3b99f612..f0fbe4e5b101b92e79449eb2686b2c19a1936592 100644 (file)
--- 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:
index e7763bbcfc67e44dfd80b17160728cbe36969c9b..85ee2b748f1e23a38db67a75ee810c2ae2d5d08a 100644 (file)
@@ -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;
   }
 
@@ -338,6 +350,28 @@ class EntityManager : boost::noncopyable {
     return View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask));
   }
 
+  /**
+   * 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.
    *
@@ -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 ...);
   }
 
index 8feb91a40c8b340d5103e28e9eeb13d750a2395b..962d858f5f82be390dde26a5fd60c9df8470ba3b 100644 (file)
@@ -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());
+};
index c2cead90b592ee38f316e60634ede4d0f5860140..1881b13fd960f90ea1fc2e5b00179487f08cae4d 100644 (file)
@@ -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());
index 5e8d8db1b0ab1f8d8a2c17be87f73c805c079d04..05a10d74b371cc3f5b9dd29763bce506443e644c 100644 (file)
@@ -21,6 +21,7 @@ void World::start() {
 void World::run() {
   running_ = true;
   double dt;
+  timer_.restart();
   while (running_) {
     dt = timer_.elapsed();
     timer_.restart();