]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Destroying an entity correctly invalidates all other references.
authorAlec Thomas <alec@swapoff.org>
Sun, 18 Aug 2013 15:33:27 +0000 (11:33 -0400)
committerAlec Thomas <alec@swapoff.org>
Sun, 18 Aug 2013 15:33:27 +0000 (11:33 -0400)
.gitignore
CHANGELOG.md
Makefile.conf [deleted file]
README.md
entityx/Entity.cc
entityx/Entity.h
entityx/Entity_test.cc
entityx/Makefile [deleted file]
entityx/python/PythonSystem.cc
entityx/python/README.md
entityx/python/entityx/tests/create_entities_from_python_test.py

index 4f2a96241fe2cbfef2570d7c887abd64c5f1e181..37d15a1f59937bf115a9769b146fe6da3a006f60 100644 (file)
@@ -4,3 +4,4 @@
 *.o
 build/*
 entityx/config.h
+Makefile
index 89c92d810c2fcc3d291e9cbf8f6b307b244b4f3b..acc00087b2f5074fc8550a44f9a3d440a01449b7 100644 (file)
@@ -1,5 +1,11 @@
 # Change Log
 
+## 2013-08-18 - Destroying an entity invalidates all other references
+
+Previously, `Entity::Id` was a simple integer index (slot) into vectors in the `EntityManager`. EntityX also maintains a list of deleted entity slots that are reused when new entities are created. This reduces the size and frequency of vector reallocation. The downside though, was that if a slot was reused, entity IDs referencing the entity before reallocation would be invalidated on reuse.
+
+Each slot now also has a version number and a "valid" bit associated with it. When an entity is allocated the version is incremented and the valid bit set. When an entity is destroyed, the valid bit is cleared. `Entity::Id` now contains all of this information and can correctly determine if an ID is still valid across destroy/create.
+
 ## 2013-08-17 - Python scripting, and a more robust build system
 
 Two big changes in this release:
diff --git a/Makefile.conf b/Makefile.conf
deleted file mode 100644 (file)
index b4e801e..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-CXX=c++
-DEBUG=-g
-CXXFLAGS=-I/usr/local/include -ansi -pedantic -Werror -Wall -Wextra -Wno-unused-parameter -Wno-error=unused-variable $(DEBUG) -std=c++11 -I.
-LDFLAGS=-lboost_signals-mt -lglog $(DEBUG)
-# Optimisations
-#CXXFLAGS += -O3 -fomit-frame-pointer -ffast-math -fstrict-aliasing -msse2
-
-# XXX Need all dependencies compiled with libc++...unlikely.
-#-stdlib=libc++
index c8d29beedbf2551948e6c61f2b56d03403d771b3..0df1d74e2e83b43ebfe397c411a8318c72d00362 100644 (file)
--- a/README.md
+++ b/README.md
@@ -6,12 +6,19 @@ Entity-Component (EC) systems are a form of decomposition that completely decoup
 
 EntityX is an EC system that uses C++11 features to provide type-safe component management, event delivery, etc. It was built during the creation of a 2D space shooter.
 
+## Contact
+
+EntityX now has a mailing list! Send a mail to [entityx@librelist.com](mailto:entityx@librelist.com) to subscribe. Instructions will follow.
+
+You can also contact me directly via [email](mailto:alec@swapoff.org) or [Twitter](https://twitter.com/alecthomas).
+
+
 ## Recent Notable Changes
 
-- Python scripting system (alpha).
-- Revamped build system, including selectable smart pointer implementation (Boost or stdlib).
+- 2013-08-18 - Destroying an entity invalidates all other references
+- 2013-08-17 - Python scripting, and a more robust build system
 
-See the [ChangeLog](https://github.com/alecthomas/entityx/blob/master/CHANGELOG.md) for a full list.
+See the [ChangeLog](https://github.com/alecthomas/entityx/blob/master/CHANGELOG.md) for details.
 
 ## Overview
 
@@ -25,7 +32,7 @@ Following is some skeleton code that implements `Position` and `Direction` compo
 
 ### Entities
 
-Entities are simply 64-bit numeric identifiers with which components are associated. Entity IDs are allocated by the `EntityManager`. Components are then associated with the entity, and can be queried or retrieved directly.
+An `Entity` is a convenience class wrapping an opaque `uint64_t` value allocated by the `EntityManager`. Each entity has a set of components associated with it that can be added, queried or retrieved directly.
 
 Creating an entity is as simple as:
 
@@ -48,6 +55,7 @@ entity.destroy();
 - 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.
+- An Entity ID contains an index and a version. When an entity is destroyed, the version associated with the index is incremented, invalidating all previous entities referencing the previous ID.
 
 ### Components (entity data)
 
index 55c273ef75f87b8ce0a55e4870c57924b4d2d36c..88be4ba36debd137d7efcfa27314b3ef3ae3390b 100644 (file)
@@ -13,6 +13,7 @@
 
 namespace entityx {
 
+const Entity::Id Entity::INVALID;
 BaseComponent::Family BaseComponent::family_counter_ = 0;
 
 void Entity::invalidate() {
@@ -21,8 +22,21 @@ void Entity::invalidate() {
 }
 
 void Entity::destroy() {
+  assert(valid());
   manager_.lock()->destroy(id_);
   invalidate();
 }
 
+bool Entity::valid() const {
+  return !manager_.expired() && manager_.lock()->valid(id_);
+}
+
+void EntityManager::destroy_all() {
+  entity_components_.clear();
+  entity_component_mask_.clear();
+  entity_version_.clear();
+  free_list_.clear();
+}
+
+
 }
index 0a8f3e7c7cfd68fce20917d80263350e03505ec2..02b41a292b080d51f772f3a6c9f2558987de804a 100644 (file)
@@ -35,20 +35,35 @@ 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.
+ * If an entity is destroyed, any copies will be invalidated. Use valid() to
+ * check for validity before using.
  */
 class Entity {
- public:
-  typedef uint64_t Id;
+public:
+  struct Id {
+    Id() : id_(0) {}
+    Id(uint32_t index, uint32_t version) : id_(uint64_t(index) | uint64_t(version) << 32UL) {}
+
+    uint64_t id() const { return id_; }
+
+    bool operator == (const Id &other) const { return id_ == other.id_; }
+    bool operator != (const Id &other) const { return id_ != other.id_; }
+    bool operator < (const Id &other) const { return id_ < other.id_; }
+
+  private:
+    friend class EntityManager;
+
+    uint32_t index() const { return id_ & 0xffffffffUL; }
+    uint32_t version() const { return id_ >> 32; }
+
+    uint64_t id_;
+  };
+
 
   /**
    * Id of an invalid Entity.
    */
-  static const Id INVALID = Id(-1);
+  static const Id INVALID;
 
   Entity() {}
 
@@ -70,17 +85,18 @@ class Entity {
   /**
    * 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.
+   * In older versions of EntityX, there were no guarantees around entity
+   * validity if a previously allocated entity slot was reassigned. That is no
+   * longer the case: if a slot is reassigned, old Entity::Id's will be
+   * invalid.
    */
-  bool valid() const {
-    return !manager_.expired() && id_ != INVALID;
-  }
+  bool valid() const;
 
   /**
    * Invalidate Entity handle, disassociating it from an EntityManager and invalidating its ID.
    *
-   * Note that this does *not* affect the underlying entity and its components.
+   * Note that this does *not* affect the underlying entity and its
+   * components. Use destroy() to destroy the associated Entity and components.
    */
   void invalidate();
 
@@ -114,6 +130,12 @@ class Entity {
 };
 
 
+inline std::ostream &operator << (std::ostream &out, const Entity::Id &id) {
+  out << "Entity::Id(" << std::hex << id.id() << ")";
+  return out;
+}
+
+
 inline std::ostream &operator << (std::ostream &out, const Entity &entity) {
   out << "Entity(" << entity.id() << ")";
   return out;
@@ -208,17 +230,18 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
    public:
     typedef boost::function<bool (entityx::shared_ptr<EntityManager>, Entity::Id)> Predicate;
 
-    /// A predicate that excludes entities that don't match the given component mask.
+    /// A predicate that matches valid entities matching the given component mask.
     class ComponentMaskPredicate {
      public:
-      ComponentMaskPredicate(const std::vector<ComponentMask> &entity_bits, ComponentMask mask) : entity_bits_(entity_bits), mask_(mask) {}
+      ComponentMaskPredicate(const std::vector<ComponentMask> &entity_id, ComponentMask mask) : entity_id_(entity_id), mask_(mask) {}
 
-      bool operator () (entityx::shared_ptr<EntityManager>, Entity::Id entity) {
-        return (entity_bits_.at(entity) & mask_) == mask_;
+      bool operator () (entityx::shared_ptr<EntityManager> entities, Entity::Id entity) {
+        return entities->entity_version_.at(entity.index()) == entity.version()
+            && (entity_id_.at(entity.index()) & mask_) == mask_;
       }
 
      private:
-      const std::vector<ComponentMask> &entity_bits_;
+      const std::vector<ComponentMask> &entity_id_;
       ComponentMask mask_;
     };
 
@@ -232,8 +255,8 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
       }
       bool operator == (const Iterator& rhs) const { return i_ == rhs.i_; }
       bool operator != (const Iterator& rhs) const { return i_ != rhs.i_; }
-      Entity operator * () { return Entity(manager_, i_); }
-      const Entity operator * () const { return Entity(manager_, i_); }
+      Entity operator * () { return Entity(manager_, manager_->create_id(i_)); }
+      const Entity operator * () const { return Entity(manager_, manager_->create_id(i_)); }
 
      private:
       friend class View;
@@ -241,25 +264,27 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
       Iterator() {}
 
       Iterator(entityx::shared_ptr<EntityManager> manager, const std::vector<Predicate> &predicates,
-               const std::vector<boost::function<void (Entity::Id)>> &unpackers, Entity::Id entity)
-          : manager_(manager), predicates_(predicates), unpackers_(unpackers), i_(entity) {
+               const std::vector<boost::function<void (Entity::Id)>> &unpackers, uint32_t index)
+          : manager_(manager), predicates_(predicates), unpackers_(unpackers), i_(index) {
         next();
       }
 
       void next() {
-        while (i_ < manager_->size() && !predicate()) {
+        while (i_ < manager_->capacity() && !predicate()) {
           ++i_;
         }
-        if (i_ < manager_->size() && !unpackers_.empty()) {
+        if (i_ < manager_->capacity() && !unpackers_.empty()) {
+          Entity::Id id = manager_->create_id(i_);
           for (auto unpacker : unpackers_) {
-            unpacker(i_);
+            unpacker(id);
           }
         }
       }
 
       bool predicate() {
+        Entity::Id id = manager_->create_id(i_);
         for (auto &p : predicates_) {
-          if (!p(manager_, i_)) {
+          if (!p(manager_, id)) {
             return false;
           }
         }
@@ -269,7 +294,7 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
       entityx::shared_ptr<EntityManager> manager_;
       const std::vector<Predicate> predicates_;
       std::vector<boost::function<void (Entity::Id)>> unpackers_;
-      Entity::Id i_;
+      uint32_t i_;
     };
 
     // Create a sub-view with an additional predicate.
@@ -278,7 +303,7 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     }
 
     Iterator begin() { return Iterator(manager_, predicates_, unpackers_, 0); }
-    Iterator end() { return Iterator(manager_, predicates_, unpackers_, manager_->size()); }
+    Iterator end() { return Iterator(manager_, predicates_, unpackers_, manager_->capacity()); }
     const Iterator begin() const { return Iterator(manager_, predicates_, unpackers_, 0); }
     const Iterator end() const { return Iterator(manager_, predicates_, unpackers_, manager_->size()); }
 
@@ -286,9 +311,9 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     View &unpack_to(entityx::shared_ptr<A> &a) {
       unpackers_.push_back(Unpacker<A>(manager_, a));
       // This resulted in a segfault under clang 4.1 on OSX. No idea why.
-      /*unpackers_.push_back([&a, this](Entity::Id id) {
-        a = manager_.component<A>(id);
-      });*/
+      // unpackers_.push_back([&a, this](uint32_t index) {
+      //   a = manager_->component<A>(Entity::Id(index, 0));
+      // });
       return *this;
     }
 
@@ -325,7 +350,19 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
   /**
    * Number of managed entities.
    */
-  size_t size() const { return entity_component_mask_.size(); }
+  size_t size() const { return entity_component_mask_.size() - free_list_.size(); }
+
+  /**
+   * Current entity capacity.
+   */
+  size_t capacity() const { return entity_component_mask_.size(); }
+
+  /**
+   * Return true if the given entity ID is still valid.
+   */
+  bool valid(Entity::Id id) {
+    return id.index() < entity_version_.size() && entity_version_.at(id.index()) == id.version();
+  }
 
   /**
    * Create a new Entity::Id.
@@ -333,16 +370,19 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
    * Emits EntityCreatedEvent.
    */
   Entity create() {
-    Entity::Id id;
+    uint32_t index, version;
     if (free_list_.empty()) {
-      id = id_counter_++;
-      accomodate_entity(id);
+      index = index_counter_++;
+      accomodate_entity(index);
+      version = entity_version_.at(index) = 1;
     } else {
-      id = free_list_.front();
+      index = free_list_.front();
       free_list_.pop_front();
+       version = entity_version_.at(index);
     }
-    event_manager_->emit<EntityCreatedEvent>(Entity(shared_from_this(), id));
-    return Entity(shared_from_this(), id);
+    Entity entity(shared_from_this(), Entity::Id(index, version));
+    event_manager_->emit<EntityCreatedEvent>(entity);
+    return entity;
   }
 
   /**
@@ -351,32 +391,45 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
    * Emits EntityDestroyedEvent.
    */
   void destroy(Entity::Id entity) {
-    assert(entity < entity_component_mask_.size() && "Entity::Id ID outside entity vector range");
+    assert(entity.index() < entity_component_mask_.size() && "Entity::Id ID outside entity vector range");
+    assert(entity_version_.at(entity.index()) == entity.version() && "Attempt to destroy Entity using a stale Entity::Id");
     event_manager_->emit<EntityDestroyedEvent>(Entity(shared_from_this(), entity));
     for (auto &components : entity_components_) {
-      components.at(entity).reset();
+      components.at(entity.index()).reset();
     }
-    entity_component_mask_.at(entity) = 0;
-    free_list_.push_back(entity);
+    entity_component_mask_.at(entity.index()) = 0;
+    entity_version_.at(entity.index())++;
+    free_list_.push_back(entity.index());
   }
 
   Entity get(Entity::Id id) {
+    assert(entity_version_.at(id.index()) != id.version() && "Attempt to get() with stale Entity::Id");
     return Entity(shared_from_this(), id);
   }
 
+  /**
+   * Create an Entity::Id for a slot.
+   *
+   * NOTE: Does *not* check for validity, but the Entity::Id constructor will
+   * fail if the ID is invalid.
+   */
+  Entity::Id create_id(uint32_t index) const {
+    return Entity::Id(index, entity_version_.at(index));
+  }
+
   /**
    * Assigns a previously constructed Component to an Entity::Id.
    *
    * @returns component
    */
   template <typename C>
-  entityx::shared_ptr<C> assign(Entity::Id entity, entityx::shared_ptr<C> component) {
+  entityx::shared_ptr<C> assign(Entity::Id id, entityx::shared_ptr<C> component) {
     entityx::shared_ptr<BaseComponent> base = entityx::static_pointer_cast<BaseComponent>(component);
     accomodate_component(C::family());
-    entity_components_.at(C::family()).at(entity) = base;
-    entity_component_mask_.at(entity) |= uint64_t(1) << C::family();
+    entity_components_.at(C::family()).at(id.index()) = base;
+    entity_component_mask_.at(id.index()) |= uint64_t(1) << C::family();
 
-    event_manager_->emit<ComponentAddedEvent<C>>(Entity(shared_from_this(), entity), component);
+    event_manager_->emit<ComponentAddedEvent<C>>(Entity(shared_from_this(), id), component);
     return component;
   }
 
@@ -403,7 +456,7 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     if (C::family() >= entity_components_.size()) {
       return entityx::shared_ptr<C>();
     }
-    entityx::shared_ptr<BaseComponent> c = entity_components_.at(C::family()).at(id);
+    entityx::shared_ptr<BaseComponent> c = entity_components_.at(C::family()).at(id.index());
     return entityx::static_pointer_cast<C>(c);
   }
 
@@ -460,6 +513,11 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     unpack<B, Args ...>(id, b, args ...);
   }
 
+  /**
+   * Destroy all entities from this EntityManager.
+   */
+  void destroy_all();
+
  private:
   EntityManager(entityx::shared_ptr<EventManager> event_manager) : event_manager_(event_manager) {}
 
@@ -486,11 +544,12 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     return component_mask<C1>(c1) | component_mask<C2, Components ...>(c2, args...);
   }
 
-  inline void accomodate_entity(Entity::Id entity) {
-    if (entity_component_mask_.size() <= entity) {
-      entity_component_mask_.resize(entity + 1);
+  inline void accomodate_entity(uint32_t index) {
+    if (entity_component_mask_.size() <= index) {
+      entity_component_mask_.resize(index + 1);
+      entity_version_.resize(index + 1);
       for (auto &components : entity_components_) {
-          components.resize(entity + 1);
+          components.resize(index + 1);
       }
     }
   }
@@ -499,20 +558,22 @@ class EntityManager : public entityx::enable_shared_from_this<EntityManager>, bo
     if (entity_components_.size() <= family) {
       entity_components_.resize(family + 1);
       for (auto &components : entity_components_) {
-          components.resize(id_counter_);
+          components.resize(index_counter_);
       }
     }
   }
 
-  Entity::Id id_counter_ = 0;
+  uint32_t index_counter_ = 0;
 
   entityx::shared_ptr<EventManager> event_manager_;
   // A nested array of: components = entity_components_[family][entity]
   std::vector<std::vector<entityx::shared_ptr<BaseComponent>>> entity_components_;
   // Bitmask of components associated with each entity. Index into the vector is the Entity::Id.
   std::vector<ComponentMask> entity_component_mask_;
-  // List of available Entity::Id IDs.
-  std::list<Entity::Id> free_list_;
+  // Vector of entity version numbers. Incremented each time an entity is destroyed
+  std::vector<uint32_t> entity_version_;
+  // List of available entity slots.
+  std::list<uint32_t> free_list_;
 };
 
 template <typename C>
@@ -524,26 +585,31 @@ BaseComponent::Family Component<C>::family() {
 
 template <typename C>
 entityx::shared_ptr<C> Entity::assign(entityx::shared_ptr<C> component) {
+  assert(valid());
   return manager_.lock()->assign<C>(id_, component);
 }
 
 template <typename C, typename ... Args>
 entityx::shared_ptr<C> Entity::assign(Args && ... args) {
+  assert(valid());
   return manager_.lock()->assign<C>(id_, args ...);
 }
 
 template <typename C>
 entityx::shared_ptr<C> Entity::component() {
+  assert(valid());
   return manager_.lock()->component<C>(id_);
 }
 
 template <typename A>
 void Entity::unpack(entityx::shared_ptr<A> &a) {
+  assert(valid());
   manager_.lock()->unpack(id_, a);
 }
 
 template <typename A, typename B, typename ... Args>
 void Entity::unpack(entityx::shared_ptr<A> &a, entityx::shared_ptr<B> &b, Args && ... args) {
+  assert(valid());
   manager_.lock()->unpack(id_, a, b, args ...);
 }
 
index 12146d1bbf690f3bd05cd6b0ad83bb407a65eb43..cbe84f7c9e11791c961b4fb1309412771c15051e 100644 (file)
@@ -96,6 +96,8 @@ TEST_F(EntityManagerTest, TestEntityAsBoolean) {
 
   e.destroy();
 
+  ASSERT_TRUE(em->size() == 0);
+
   ASSERT_TRUE(!e);
 
   Entity e2; // Not initialized
@@ -104,11 +106,19 @@ TEST_F(EntityManagerTest, TestEntityAsBoolean) {
 
 TEST_F(EntityManagerTest, TestEntityReuse) {
   Entity e1 = em->create();
+  Entity e2 = e1;
   auto id = e1.id();
+  ASSERT_TRUE(e1.valid());
+  ASSERT_TRUE(e2.valid());
   e1.destroy();
-  Entity e2 = em->create();
-  // It is assumed that the allocation will reuse the same entity id.
-  ASSERT_EQ(e2.id(), id);
+  ASSERT_TRUE(!e1.valid());
+  ASSERT_TRUE(!e2.valid());
+  Entity e3 = em->create();
+  // It is assumed that the allocation will reuse the same entity id, though
+  // the version will change.
+  auto new_id = e3.id();
+  ASSERT_NE(new_id, id);
+  ASSERT_EQ(new_id.id() & 0xffffffffUL, id.id() & 0xffffffffUL);
 }
 
 TEST_F(EntityManagerTest, TestComponentConstruction) {
@@ -334,3 +344,10 @@ TEST_F(EntityManagerTest, TestEntityAssignment) {
   a.invalidate();
   ASSERT_NE(a, b);
 }
+
+TEST_F(EntityManagerTest, TestEntityDestroyAll) {
+  Entity a = em->create(), b = em->create();
+  em->destroy_all();
+  ASSERT_FALSE(a.valid());
+  ASSERT_FALSE(b.valid());
+}
diff --git a/entityx/Makefile b/entityx/Makefile
deleted file mode 100644 (file)
index fd2da1c..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-include ../Makefile.conf
-
-S=Event.cc Entity.cc System.cc World.cc Components.cc
-L=libentity.a
-SL=libentity.so
-O=$(S:.cc=.o)
-
-TESTS=Entity_test.cc System_test.cc Event_test.cc Components_test.cc
-TESTS_O=$(TESTS:.cc=.o)
-TESTS_BIN=$(TESTS:.cc=)
-ALL_TESTS_BIN=.all_tests
-
-ALL_SOURCES=$(S) $(TESTS)
-ALL_O=$(ALL_SOURCES:.cc=.o)
-
-ALL_DEPS=$(patsubst %.cc,.%.cc.dep,$(ALL_SOURCES))
-
-LDFLAGS+=libentity.a
-CXXFLAGS+=-I..
-
-.PHONY: all clean distclean test cleantests
-
-all: $(L) $(SL)
-
-# Consolidated test binary
-test: $(ALL_TESTS_BIN)
-       @./$(ALL_TESTS_BIN)
-
-$(ALL_TESTS_BIN): $(TESTS_O) $(L)
-       $(CXX) $(LDFLAGS) -lgtest -lgtest_main $(TESTS_O) -o $@
-
-# Individual test binaries
-$(TESTS_BIN): $(TESTS_O) $(L)
-       $(CXX) $(LDFLAGS) -lgtest -lgtest_main $@.o -o $@
-
-$(L): $(O)
-       ar rc $(L) $(O)
-
-$(SL): $(O)
-       $(CXX) $(LDFLAGS) -fPIC -shared -o $@ $(O)
-
-%.o: %.cc
-       $(CXX) $(CXXFLAGS) -c $< -o $@
-
-# Command targets
-
-clean: cleantests
-       rm -f $(SL) $(O) $(L)
-
-cleantests:
-       rm -f $(TESTS_O) $(TESTS_BIN) $(ALL_TESTS_BIN)
-
-distclean: clean
-       rm -f $(ALL_DEPS)
-
--include $(ALL_DEPS)
-
-.%.cc.dep: %.cc %.h
-       @touch $@
-       @makedepend -f$@ -I.. $^ > /dev/null 2>&1
-       @rm -f $@.bak
-
-.%.cc.dep: %.cc
-       @touch $@
-       @makedepend -f$@ -I.. $< > /dev/null 2>&1
-       @rm -f $@.bak
index 028ab503886cd698a1a98170d8f04d1a10d010d7..db6b150a09c923cc83ab3db88edfc4d35277c588 100644 (file)
@@ -27,6 +27,16 @@ namespace python {
 static const py::object None;
 
 
+/**
+ * Convert Entity::Id to a Python long.
+ */
+struct EntityIdToPythonInteger {
+  static PyObject* convert(Entity::Id const& id) {
+    return py::incref(py::long_(id.id()).ptr());
+  }
+};
+
+
 class PythonEntityXLogger {
 public:
   PythonEntityXLogger() {}
@@ -59,8 +69,14 @@ static std::string entity_repr(Entity entity) {
   return repr.str();
 }
 
+static bool entity_eq(Entity left, Entity right) {
+  return left.id() == right.id();
+}
+
 
 BOOST_PYTHON_MODULE(_entityx) {
+  py::to_python_converter<Entity::Id, EntityIdToPythonInteger>();
+
   py::class_<PythonEntityXLogger>("Logger", py::no_init)
     .def("write", &PythonEntityXLogger::write);
 
@@ -71,6 +87,7 @@ BOOST_PYTHON_MODULE(_entityx) {
 
   py::class_<Entity>("RawEntity", py::no_init)
     .add_property("id", &Entity::id)
+    .def("__eq__", &entity_eq)
     .def("__repr__", &entity_repr);
 
   py::class_<PythonComponent, entityx::shared_ptr<PythonComponent>>("PythonComponent", py::init<py::object>())
@@ -184,7 +201,8 @@ void PythonSystem::receive(const ComponentAddedEvent<PythonComponent> &event) {
   // associated with it. Create one.
   if (!event.component->object) {
     py::object module = py::import(event.component->module.c_str());
-    py::object from_raw_entity = module.attr(event.component->cls.c_str()).attr("_from_raw_entity");
+    py::object cls = module.attr(event.component->cls.c_str());
+    py::object from_raw_entity = cls.attr("_from_raw_entity");
     if (py::len(event.component->args) == 0) {
       event.component->object = from_raw_entity(event.entity);
     } else {
index f4f6aa2231bbac75d2cd2c3655e71f6cc1ba00b6..9b069037a84c8400c0dd4984c0b35a8ae89f13bc 100644 (file)
@@ -10,7 +10,7 @@ Planned features that are currently unimplemented:
 
 ## Design
 
-- Python scripts are attached to entities with `PythonComponent`.
+- Python scripts are attached to entities via `PythonComponent`.
 - Systems and components can not be created from Python, primarily for performance reasons.
 - Events are proxied directly to Python entities via `PythonEventProxy` objects.
     - Each event to be handled in Python must have an associated `PythonEventProxy`implementation.
index 41e8a9a5a53b7499640e67c41ed4ac18276e362b..e71fa2160bfb889869bdd864b22eca116f0c1eb6 100644 (file)
@@ -5,17 +5,29 @@ from entityx_python_test import Position
 class EntityA(entityx.Entity):
     position = entityx.Component(Position, 1, 2)
 
+    def __init__(self, x=None, y=None):
+        if x is not None:
+            self.position.x = x
+        if y is not None:
+            self.position.y = y
+
 
 def create_entities_from_python_test():
     a = EntityA()
-    assert a._entity.id == 0
+    assert a._entity.id & 0xffffffff == 0
     assert a.position.x == 1.0
     assert a.position.y == 2.0
 
     b = EntityA()
-    assert b._entity.id == 1
+    assert b._entity.id & 0xffffffff == 1
 
     a.destroy()
     c = EntityA()
-    # Reuse destroyed entitys ID.
-    assert c._entity.id == 0
+    # Reuse destroyed index of "a".
+    assert c._entity.id & 0xffffffff == 0
+    # However, version is different
+    assert a._entity.id != c._entity.id and c._entity.id > a._entity.id
+
+    d = EntityA(2.0, 3.0)
+    assert d.position.x == 2.0
+    assert d.position.y == 3.0