aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlec Thomas <alec@swapoff.org>2013-08-18 11:33:27 -0400
committerAlec Thomas <alec@swapoff.org>2013-08-18 11:33:27 -0400
commitb0fe85294568485b1cd06afa0e9ab9b7b0beea62 (patch)
tree428498e013351eaaad728bd1d1cb66a1872338f4
parent85acbac9ef2838926e367fbd82ca5964668744d0 (diff)
Destroying an entity correctly invalidates all other references.
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.md6
-rw-r--r--Makefile.conf9
-rw-r--r--README.md16
-rw-r--r--entityx/Entity.cc14
-rw-r--r--entityx/Entity.h178
-rw-r--r--entityx/Entity_test.cc23
-rw-r--r--entityx/Makefile66
-rw-r--r--entityx/python/PythonSystem.cc20
-rw-r--r--entityx/python/README.md2
-rw-r--r--entityx/python/entityx/tests/create_entities_from_python_test.py20
11 files changed, 211 insertions, 144 deletions
diff --git a/.gitignore b/.gitignore
index 4f2a962..37d15a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
*.o
build/*
entityx/config.h
+Makefile
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89c92d8..acc0008 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
index b4e801e..0000000
--- a/Makefile.conf
+++ /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++
diff --git a/README.md b/README.md
index c8d29be..0df1d74 100644
--- 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)
diff --git a/entityx/Entity.cc b/entityx/Entity.cc
index 55c273e..88be4ba 100644
--- a/entityx/Entity.cc
+++ b/entityx/Entity.cc
@@ -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();
+}
+
+
}
diff --git a/entityx/Entity.h b/entityx/Entity.h
index 0a8f3e7..02b41a2 100644
--- a/entityx/Entity.h
+++ b/entityx/Entity.h
@@ -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 ...);
}
diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc
index 12146d1..cbe84f7 100644
--- a/entityx/Entity_test.cc
+++ b/entityx/Entity_test.cc
@@ -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
index fd2da1c..0000000
--- a/entityx/Makefile
+++ /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
diff --git a/entityx/python/PythonSystem.cc b/entityx/python/PythonSystem.cc
index 028ab50..db6b150 100644
--- a/entityx/python/PythonSystem.cc
+++ b/entityx/python/PythonSystem.cc
@@ -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 {
diff --git a/entityx/python/README.md b/entityx/python/README.md
index f4f6aa2..9b06903 100644
--- a/entityx/python/README.md
+++ b/entityx/python/README.md
@@ -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.
diff --git a/entityx/python/entityx/tests/create_entities_from_python_test.py b/entityx/python/entityx/tests/create_entities_from_python_test.py
index 41e8a9a..e71fa21 100644
--- a/entityx/python/entityx/tests/create_entities_from_python_test.py
+++ b/entityx/python/entityx/tests/create_entities_from_python_test.py
@@ -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