]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Some breaking changes to Entity management.
authorAlec Thomas <alec@swapoff.org>
Mon, 11 Mar 2013 05:38:06 +0000 (01:38 -0400)
committerAlec Thomas <alec@swapoff.org>
Mon, 11 Mar 2013 05:38:06 +0000 (01:38 -0400)
- Entity will no longer implicitly cast to Entity::Id. This is safer
  and cleaner.
- Use Entity::destroy(). This will destroy the entity *and* invalidate
  the handle.
- Removed the exists() method, as it was largely meaningless anyway.
  Listen to EntityDestroyedEvent instead.
- Exposed Entity::manager() and removed manager attribute from events.

CMakeLists.txt
README.md
entityx/Components_test.cc
entityx/Entity.cc
entityx/Entity.h
entityx/Entity_test.cc
entityx/System_test.cc

index c4547c27b974b4d6922c550476d86f1c633494af..7ee2745468f64a495834d5cd0a84ccc159d78c43 100644 (file)
@@ -23,6 +23,7 @@ macro(create_test TARGET_NAME SOURCE)
         entityx
         glog
         ${Boost_LIBRARIES}
+        ${Boost_SIGNALS_LIBRARY}
         ${GTEST_BOTH_LIBRARIES}
         )
     add_test(${TARGET_NAME} ${TARGET_NAME})
@@ -60,6 +61,11 @@ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
 set(sources entityx/Components.cc entityx/System.cc entityx/Event.cc entityx/Entity.cc entityx/Manager.cc)
 add_library(entityx STATIC ${sources})
 add_library(entityx_shared SHARED ${sources})
+target_link_libraries(
+    entityx_shared
+    ${Boost_SIGNALS_LIBRARY}
+    glog
+)
 set_target_properties(entityx_shared PROPERTIES OUTPUT_NAME entityx)
 
 include_directories(
index 45656770877477b3204be226bde8aea203dbd392..4ac3b47b60074b0fe7c21d595cc33fb362e2a8ed 100644 (file)
--- a/README.md
+++ b/README.md
@@ -29,13 +29,15 @@ Entity entity = entities.create();
 And destroying an entity is done with:
 
 ```c++
-entities.destroy(entity);
+entity.destroy();
 ```
 
-#### Testing for destroyed entities
+#### Implementation details
 
-The Entity type can be seen as a safe pointer.
-Test if it is valid with `entity.exists()`.
+- Each Entity is a convenience class wrapping an Entity::Id.
+- An Entity handle can be invalidated with `invalidate()`. This does not affect the underlying entity.
+- When an entity is destroyed the manager adds its ID to a free list and invalidates the Entity handle.
+- When an entity is created IDs are recycled from the free list before allocating new ones.
 
 ### Components (entity data)
 
@@ -43,11 +45,6 @@ The idea with ECS is to not have any functionality in the component. All logic s
 
 To that end Components are typically [POD types](http://en.wikipedia.org/wiki/Plain_Old_Data_Structures) 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>`.
 
-Implementation notes:
-
-- Components must provide a no-argument constructor.
-- The current implementation can handle up to 64 components in total.
-
 #### Creating components
 
 As an example, position and direction information might be represented as:
@@ -103,6 +100,11 @@ if (position) {
 }
 ```
 
+#### Implementation notes
+
+- Components must provide a no-argument constructor.
+- The current implementation can handle up to 64 components in total. This can be extended with little effort.
+
 ### Systems (implementing behavior)
 
 Systems implement behavior using one or more components. Implementations are subclasses of `System<T>` and *must* implement the `update()` method, as shown below.
@@ -187,7 +189,22 @@ DebugCollisions debug_collisions;
 events.subscribe<Collision>(debug_collisions);
 ```
 
-There can be more than one subscriber for an event; each one will be called.
+#### Builtin events
+
+Several events are emitted by EntityX itself:
+
+- `EntityCreatedEvent` - emitted when a new Entity has been created.
+  - `Entity entity` - Newly created Entity.
+- `EntityDestroyedEvent` - emitted when an Entity is *about to be* destroyed.
+  - `Entity entity` - Entity about to be destroyed.
+- `ComponentAddedEvent<T>` - emitted when a new component is added to an entity.
+  - `Entity entity` - Entity that component was added to.
+  - `boost::shared_ptr<T> component` - The component added.
+
+#### Implementation notes
+
+- There can be more than one subscriber for an event; each one will be called.
+- Event objects are destroyed after delivery, so references should not be retained.
 
 ### Manager (tying it all together)
 
index 8c4d210062a1c5d80a3172dcbc911a7dafeaeaf4..d9a0ecd75c02bb5e63c64410e96f4a628548359c 100644 (file)
@@ -41,14 +41,14 @@ TEST(TagsComponentTest, TestVariadicConstruction) {
 TEST(TagsComponentTest, TestEntitiesWithTag) {
   EventManager ev;
   EntityManager en(ev);
-  Entity::Id a = en.create();
-  en.assign<Position>(a);
+  Entity a = en.create();
+  a.assign<Position>();
   for (int i = 0; i < 99; ++i) {
     auto e = en.create();
-    en.assign<Position>(e);
-    en.assign<TagsComponent>(e, "positionable");
+    e.assign<Position>();
+    e.assign<TagsComponent>("positionable");
   }
-  en.assign<TagsComponent>(a, "player", "indestructible");
+  a.assign<TagsComponent>("player", "indestructible");
   auto entities = en.entities_with_components<Position>();
   ASSERT_EQ(100, size(entities));
   ASSERT_EQ(1, size(TagsComponent::view(entities, "player")));
index a17813fd15130245f50c5476e8cd283a2cdaa989..039cc4a258ec35876156311bd1bf8b5a3356fd94 100644 (file)
@@ -17,13 +17,14 @@ const Entity::Id Entity::INVALID = Entity::Id(-1);
 
 BaseComponent::Family BaseComponent::family_counter_ = 0;
 
-bool Entity::exists() const {
-  return entities_ != nullptr && entities_->exists(id_);
+void Entity::invalidate() {
+  id_ = INVALID;
+  manager_ = nullptr;
 }
 
-void Entity::detach() {
-  id_ = INVALID;
-  entities_ = nullptr;
+void Entity::destroy() {
+  manager_->destroy(id_);
+  invalidate();
 }
 
 }
index f5028b074fa696f6c013a2817bcae927e958e2c4..e9c5d3f4c6a3d218d0bf157e5a2cda718c108f66 100644 (file)
@@ -34,6 +34,12 @@ class EntityManager;
 
 /**
  * A convenience handle around an Entity::Id.
+ *
+ * NOTE: Because Entity IDs are recycled by the EntityManager, there is no way
+ * to determine if an Entity is still associated with the same logical entity.
+ *
+ * The only way to robustly track an Entity through its lifecycle is to
+ * subscribe to EntityDestroyedEvent.
  */
 class Entity {
  public:
@@ -47,30 +53,39 @@ class Entity {
   Entity() {}
 
   /**
-   * Alias for exists().
+   * Check if Entity handle is invalid.
    */
   bool operator ! () const {
-    return !exists();
+    return !valid();
   }
 
   bool operator == (const Entity &other) const {
-    return other.entities_ == entities_ && other.id_ == id_;
+    return other.manager_ == manager_ && other.id_ == id_;
   }
 
   bool operator != (const Entity &other) const {
-    return other.entities_ != entities_ || other.id_ != id_;
+    return other.manager_ != manager_ || other.id_ != id_;
   }
 
   /**
-   * Detach Entity handle from the EntityManager.
+   * Is this Entity handle valid?
+   *
+   * *NOTE:* This does *not* imply that the originally associated entity ID is
+   * the same, or is valid in any way.
    */
-  void detach();
-
-  Id id() const { return id_; }
+  bool valid() const {
+    return manager_ != nullptr && id_ != INVALID;
+  }
 
-  operator Id () { return id_; }
+  /**
+   * Invalidate Entity handle, disassociating it from an EntityManager and invalidating its ID.
+   *
+   * Note that this does *not* affect the underlying entity and its components.
+   */
+  void invalidate();
 
-  bool exists() const;
+  Id id() const { return id_; }
+  EntityManager &manager() { return *manager_; }
 
   template <typename C>
   boost::shared_ptr<C> assign(boost::shared_ptr<C> component);
@@ -85,12 +100,17 @@ class Entity {
   template <typename A, typename B, typename ... Args>
   void unpack(boost::shared_ptr<A> &a, boost::shared_ptr<B> &b, Args && ... args);
 
+  /**
+   * Destroy and invalidate this Entity.
+   */
+  void destroy();
+
  private:
   friend class EntityManager;
 
-  Entity(EntityManager *entities, Entity::Id id) : entities_(entities), id_(id) {}
+  Entity(EntityManager *entities, Entity::Id id) : manager_(entities), id_(id) {}
 
-  EntityManager *entities_ = nullptr;
+  EntityManager *manager_ = nullptr;
   Entity::Id id_ = INVALID;
 };
 
@@ -150,20 +170,16 @@ struct Component : public BaseComponent {
  * Emitted when an entity is added to the system.
  */
 struct EntityCreatedEvent : public Event<EntityCreatedEvent> {
-  EntityCreatedEvent(EntityManager &manager, Entity::Id entity) :
-      manager(manager), entity(entity) {}
+  EntityCreatedEvent(Entity entity) : entity(entity) {}
 
-  EntityManager &manager;
-  Entity::Id entity;
+  Entity entity;
 };
 
 
 struct EntityDestroyedEvent : public Event<EntityDestroyedEvent> {
-  EntityDestroyedEvent(EntityManager &manager, Entity::Id entity) :
-      manager(manager), entity(entity) {}
+  EntityDestroyedEvent(Entity entity) : entity(entity) {}
 
-  EntityManager &manager;
-  Entity::Id entity;
+  Entity entity;
 };
 
 
@@ -172,11 +188,10 @@ struct EntityDestroyedEvent : public Event<EntityDestroyedEvent> {
  */
 template <typename T>
 struct ComponentAddedEvent : public Event<ComponentAddedEvent<T>> {
-  ComponentAddedEvent(EntityManager &manager, Entity::Id entity, boost::shared_ptr<T> component) :
-      manager(manager), entity(entity), component(component) {}
+  ComponentAddedEvent(Entity entity, boost::shared_ptr<T> component) :
+      entity(entity), component(component) {}
 
-  EntityManager &manager;
-  Entity::Id entity;
+  Entity entity;
   boost::shared_ptr<T> component;
 };
 
@@ -339,7 +354,7 @@ class EntityManager : boost::noncopyable {
       id = *it;
       free_list_.erase(it);
     }
-    event_manager_.emit<EntityCreatedEvent>(*this, id);
+    event_manager_.emit<EntityCreatedEvent>(Entity(this, id));
     return Entity(this, id);
   }
 
@@ -350,7 +365,7 @@ class EntityManager : boost::noncopyable {
    */
   void destroy(Entity::Id entity) {
     CHECK(entity < entity_component_mask_.size()) << "Entity::Id ID outside entity vector range";
-    event_manager_.emit<EntityDestroyedEvent>(*this, entity);
+    event_manager_.emit<EntityDestroyedEvent>(Entity(this, entity));
     for (auto &components : entity_components_) {
       components.at(entity).reset();
     }
@@ -358,16 +373,6 @@ class EntityManager : boost::noncopyable {
     free_list_.insert(entity);
   }
 
-  /**
-   * Check if an Entity::Id is registered.
-   */
-  bool exists(Entity::Id entity) {
-    if (entity_component_mask_.empty() || entity >= id_counter_) {
-      return false;
-    }
-    return free_list_.find(entity) == free_list_.end();
-  }
-
   Entity get(Entity::Id id) {
     return Entity(this, id);
   }
@@ -384,7 +389,7 @@ class EntityManager : boost::noncopyable {
     entity_components_.at(C::family()).at(entity) = base;
     entity_component_mask_.at(entity) |= uint64_t(1) << C::family();
 
-    event_manager_.emit<ComponentAddedEvent<C>>(*this, entity, component);
+    event_manager_.emit<ComponentAddedEvent<C>>(Entity(this, entity), component);
     return component;
   }
 
@@ -520,27 +525,27 @@ class EntityManager : boost::noncopyable {
 
 template <typename C>
 boost::shared_ptr<C> Entity::assign(boost::shared_ptr<C> component) {
-  return entities_->assign<C>(id_, component);
+  return manager_->assign<C>(id_, component);
 }
 
 template <typename C, typename ... Args>
 boost::shared_ptr<C> Entity::assign(Args && ... args) {
-  return entities_->assign<C>(id_, args ...);
+  return manager_->assign<C>(id_, args ...);
 }
 
 template <typename C>
 boost::shared_ptr<C> Entity::component() {
-  return entities_->component<C>(id_);
+  return manager_->component<C>(id_);
 }
 
 template <typename A>
 void Entity::unpack(boost::shared_ptr<A> &a) {
-  entities_->unpack(id_, a);
+  manager_->unpack(id_, a);
 }
 
 template <typename A, typename B, typename ... Args>
 void Entity::unpack(boost::shared_ptr<A> &a, boost::shared_ptr<B> &b, Args && ... args) {
-  entities_->unpack(id_, a, b, args ...);
+  manager_->unpack(id_, a, b, args ...);
 }
 
 }
index 36a607b619ddcbc65ac59b4b8a8ff3bae82bf535..828ae17e263d4600868f69551601ab94d7316d65 100644 (file)
@@ -79,27 +79,24 @@ TEST_F(EntityManagerTest, TestCreateEntity) {
   ASSERT_TRUE(em.size() == 0);
 
   Entity e2;
-  ASSERT_FALSE(e2.exists());
-  ASSERT_FALSE(em.exists(e2));
+  ASSERT_FALSE(e2.valid());
 
   Entity e = em.create();
-  ASSERT_TRUE(e.exists());
-  ASSERT_TRUE(em.exists(e));
+  ASSERT_TRUE(e.valid());
   ASSERT_TRUE(em.size() == 1);
 
   e2 = e;
-  ASSERT_TRUE(e2.exists());
-  ASSERT_TRUE(em.exists(e2));
+  ASSERT_TRUE(e2.valid());
 }
 
 TEST_F(EntityManagerTest, TestEntityAsBoolean) {
   ASSERT_TRUE(em.size() == 0);
   Entity e = em.create();
-  ASSERT_TRUE(em.exists(e));
+  ASSERT_TRUE(e.valid());
   ASSERT_TRUE(em.size() == 1);
   ASSERT_FALSE(!e);
 
-  em.destroy(e);
+  e.destroy();
 
   ASSERT_TRUE(!e);
 
@@ -110,8 +107,7 @@ TEST_F(EntityManagerTest, TestEntityAsBoolean) {
 TEST_F(EntityManagerTest, TestEntityReuse) {
   Entity e1 = em.create();
   auto id = e1.id();
-  em.destroy(e1);
-  ASSERT_TRUE(!em.exists(e1));
+  e1.destroy();
   Entity e2 = em.create();
   // It is assumed that the allocation will reuse the same entity id.
   ASSERT_EQ(e2.id(), id);
@@ -141,23 +137,21 @@ TEST_F(EntityManagerTest, TestDestroyEntity) {
   Entity f = em.create();
   auto ep = e.assign<Position>();
   f.assign<Position>();
-  e.assign<Direction>(e);
+  e.assign<Direction>();
   f.assign<Direction>();
 
   ASSERT_EQ(2, ep.use_count());
-  ASSERT_TRUE(em.exists(e));
-  ASSERT_TRUE(em.exists(f));
+  ASSERT_TRUE(e.valid());
+  ASSERT_TRUE(f.valid());
   ASSERT_TRUE(e.component<Position>());
   ASSERT_TRUE(e.component<Direction>());
   ASSERT_TRUE(f.component<Position>());
   ASSERT_TRUE(f.component<Direction>());
 
-  em.destroy(e);
+  e.destroy();
 
-  ASSERT_FALSE(em.exists(e));
-  ASSERT_TRUE(em.exists(f));
-  ASSERT_FALSE(e.component<Position>());
-  ASSERT_FALSE(e.component<Direction>());
+  ASSERT_FALSE(e.valid());
+  ASSERT_TRUE(f.valid());
   ASSERT_TRUE(f.component<Position>());
   ASSERT_TRUE(f.component<Direction>());
   ASSERT_EQ(1, ep.use_count());
@@ -176,7 +170,7 @@ TEST_F(EntityManagerTest, TestGetEntitiesWithComponent) {
 }
 
 TEST_F(EntityManagerTest, TestGetEntitiesWithIntersectionOfComponents) {
-  vector<Entity::Id> entities;
+  vector<Entity> entities;
   for (int i = 0; i < 150; ++i) {
     Entity e = em.create();
     entities.push_back(e);
@@ -193,17 +187,17 @@ TEST_F(EntityManagerTest, TestGetEntitiesWithIntersectionOfComponents) {
 
 TEST_F(EntityManagerTest, TestGetEntitiesWithComponentAndUnpacking) {
   vector<Entity::Id> entities;
-  Entity::Id e = em.create();
-  Entity::Id f = em.create();
-  Entity::Id g = em.create();
+  Entity e = em.create();
+  Entity f = em.create();
+  Entity g = em.create();
   std::vector<std::pair<shared_ptr<Position>, 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)));
+          e.assign<Position>(1.0f, 2.0f),
+          e.assign<Direction>(3.0f, 4.0f)));
   position_directions.push_back(std::make_pair(
-          em.assign<Position>(f, 7.0f, 8.0f),
-          em.assign<Direction>(f, 9.0f, 10.0f)));
-  em.assign<Position>(g, 5.0f, 6.0f);
+          f.assign<Position>(7.0f, 8.0f),
+          f.assign<Direction>(9.0f, 10.0f)));
+  g.assign<Position>(5.0f, 6.0f);
   int i = 0;
 
   shared_ptr<Position> position;
@@ -221,13 +215,13 @@ TEST_F(EntityManagerTest, TestGetEntitiesWithComponentAndUnpacking) {
 }
 
 TEST_F(EntityManagerTest, TestUnpack) {
-  Entity::Id e = em.create();
-  auto p = em.assign<Position>(e);
-  auto d = em.assign<Direction>(e);
+  Entity e = em.create();
+  auto p = e.assign<Position>();
+  auto d = e.assign<Direction>();
 
   shared_ptr<Position> up;
   shared_ptr<Direction> ud;
-  em.unpack<Position, Direction>(e, up, ud);
+  e.unpack<Position, Direction>(up, ud);
   ASSERT_EQ(p, up);
   ASSERT_EQ(d, ud);
 }
@@ -236,12 +230,12 @@ TEST_F(EntityManagerTest, TestUnpack) {
 struct NullDeleter {template<typename T> void operator()(T*) {} };
 
 TEST_F(EntityManagerTest, TestUnpackNullMissing) {
-  Entity::Id e = em.create();
-  auto p = em.assign<Position>(e);
+  Entity e = em.create();
+  auto p = e.assign<Position>();
 
   shared_ptr<Position> up(reinterpret_cast<Position*>(0Xdeadbeef), NullDeleter());
   shared_ptr<Direction> ud(reinterpret_cast<Direction*>(0Xdeadbeef), NullDeleter());
-  em.unpack<Position, Direction>(e, up, ud);
+  e.unpack<Position, Direction>(up, ud);
   ASSERT_EQ(p, up);
   ASSERT_EQ(shared_ptr<Direction>(), ud);
 }
@@ -256,7 +250,7 @@ TEST_F(EntityManagerTest, TestEntityCreatedEvent) {
       created.push_back(event.entity);
     }
 
-    vector<Entity::Id> created;
+    vector<Entity> created;
   };
 
   EntityCreatedEventReceiver receiver;
@@ -275,20 +269,20 @@ TEST_F(EntityManagerTest, TestEntityDestroyedEvent) {
       destroyed.push_back(event.entity);
     }
 
-    vector<Entity::Id> destroyed;
+    vector<Entity> destroyed;
   };
 
   EntityDestroyedEventReceiver receiver;
   ev.subscribe<EntityDestroyedEvent>(receiver);
 
   ASSERT_EQ(0, receiver.destroyed.size());
-  vector<Entity::Id> entities;
+  vector<Entity> entities;
   for (int i = 0; i < 10; ++i) {
     entities.push_back(em.create());
   }
   ASSERT_EQ(0, receiver.destroyed.size());
   for (auto e : entities) {
-    em.destroy(e);
+    e.destroy();
   }
   ASSERT_TRUE(entities == receiver.destroyed);
 }
@@ -325,9 +319,9 @@ TEST_F(EntityManagerTest, TestComponentAddedEvent) {
   ASSERT_EQ(0, receiver.position_events);
   ASSERT_EQ(0, receiver.direction_events);
   for (int i = 0; i < 10; ++i) {
-    Entity::Id e = em.create();
-    em.assign<Position>(e, float(i), float(i));
-    em.assign<Direction>(e, float(-i), float(-i));
+    Entity e = em.create();
+    e.assign<Position>(float(i), float(i));
+    e.assign<Direction>(float(-i), float(-i));
   }
   ASSERT_EQ(10, receiver.position_events);
   ASSERT_EQ(10, receiver.direction_events);
@@ -339,6 +333,6 @@ TEST_F(EntityManagerTest, TestEntityAssignment) {
   ASSERT_NE(a, b);
   b = a;
   ASSERT_EQ(a, b);
-  a.detach();
+  a.invalidate();
   ASSERT_NE(a, b);
 }
index c529bd1e93a3034e291175318889ba115c198f24..85b08da09231e0a8a04ea007eaddd6b80f69be5b 100644 (file)
@@ -43,7 +43,7 @@ class MovementSystem : public System<MovementSystem> {
     shared_ptr<Position> position;
     shared_ptr<Direction> direction;
     for (auto entity : entities) {
-      es.unpack<Position, Direction>(entity, position, direction);
+      entity.unpack<Position, Direction>(position, direction);
       position->x += direction->x;
       position->y += direction->y;
     }
@@ -55,7 +55,7 @@ class MovementSystem : public System<MovementSystem> {
 
 class TestManager : public entityx::Manager {
  public:
-  std::vector<Entity::Id> entities;
+  std::vector<Entity> entities;
 
   SystemManager &sm() { return system_manager; }
   EntityManager &em() { return entity_manager; }
@@ -66,12 +66,12 @@ class TestManager : public entityx::Manager {
 
   void initialize() override {
     for (int i = 0; i < 150; ++i) {
-      Entity::Id e = entity_manager.create();
+      Entity e = entity_manager.create();
       entities.push_back(e);
       if (i % 2 == 0)
-        entity_manager.assign<Position>(e, 1, 2);
+        e.assign<Position>(1, 2);
       if (i % 3 == 0)
-        entity_manager.assign<Direction>(e, 1, 1);
+        e.assign<Direction>(1, 1);
     }
   }
 
@@ -106,7 +106,7 @@ TEST_F(SystemManagerTest, TestApplySystem) {
   shared_ptr<Position> position;
   shared_ptr<Direction> direction;
   for (auto entity : manager.entities) {
-    manager.em().unpack<Position, Direction>(entity, position, direction);
+    entity.unpack<Position, Direction>(position, direction);
     if (position && direction) {
       ASSERT_FLOAT_EQ(2.0, position->x);
       ASSERT_FLOAT_EQ(3.0, position->y);