aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt6
-rw-r--r--README.md37
-rw-r--r--entityx/Components_test.cc10
-rw-r--r--entityx/Entity.cc11
-rw-r--r--entityx/Entity.h89
-rw-r--r--entityx/Entity_test.cc78
-rw-r--r--entityx/System_test.cc12
7 files changed, 133 insertions, 110 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c4547c2..7ee2745 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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(
diff --git a/README.md b/README.md
index 4565677..4ac3b47 100644
--- 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)
diff --git a/entityx/Components_test.cc b/entityx/Components_test.cc
index 8c4d210..d9a0ecd 100644
--- a/entityx/Components_test.cc
+++ b/entityx/Components_test.cc
@@ -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")));
diff --git a/entityx/Entity.cc b/entityx/Entity.cc
index a17813f..039cc4a 100644
--- a/entityx/Entity.cc
+++ b/entityx/Entity.cc
@@ -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();
}
}
diff --git a/entityx/Entity.h b/entityx/Entity.h
index f5028b0..e9c5d3f 100644
--- a/entityx/Entity.h
+++ b/entityx/Entity.h
@@ -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 ...);
}
}
diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc
index 36a607b..828ae17 100644
--- a/entityx/Entity_test.cc
+++ b/entityx/Entity_test.cc
@@ -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);
}
diff --git a/entityx/System_test.cc b/entityx/System_test.cc
index c529bd1..85b08da 100644
--- a/entityx/System_test.cc
+++ b/entityx/System_test.cc
@@ -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);