Skip to content

Commit

Permalink
Removed Manager. Add EntityX class. Largely eradicate use of shared_ptr.
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Mar 2, 2014
1 parent 644d056 commit f212094
Show file tree
Hide file tree
Showing 21 changed files with 274 additions and 445 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Expand Up @@ -122,7 +122,7 @@ endif()
# Things to install
set(install_libs entityx)

set(sources entityx/tags/TagsComponent.cc entityx/deps/Dependencies.cc entityx/System.cc entityx/Event.cc entityx/Entity.cc entityx/Manager.cc entityx/help/Timer.cc)
set(sources entityx/System.cc entityx/Event.cc entityx/Entity.cc entityx/help/Timer.cc)
add_library(entityx STATIC ${sources})

if (ENTITYX_BUILD_SHARED)
Expand Down
55 changes: 25 additions & 30 deletions README.md
Expand Up @@ -27,6 +27,7 @@ You can also contact me directly via [email](mailto:alec@swapoff.org) or [Twitte

## Recent Notable Changes

- 2014-03-02 - (1.0.0alpha1) Switch to using cache friendly component storage (big breaking change). Also largely eradicated use of `std::shared_ptr`.
- 2014-02-13 - Visual C++ support thanks to [Jarrett Chisholm](https://github.com/jarrettchisholm)!
- 2013-10-29 - Boost has been removed as a primary dependency for builds not using python.
- 2013-08-21 - Remove dependency on `boost::signal` and switch to embedded [Simple::Signal](http://timj.testbit.eu/2013/cpp11-signal-system-performance/).
Expand Down Expand Up @@ -58,10 +59,11 @@ An `entityx::Entity` is a convenience class wrapping an opaque `uint64_t` value
Creating an entity is as simple as:

```c++
entityx::ptr<entityx::EventManager> events(new entityx::EventManager());
entityx::ptr<entityx::EntityManager> entities(new entityx::EntityManager(event));
#include <entityx/entityx.h>

entityx::Entity entity = entities->create();
EntityX entityx;

entityx::Entity entity = entityx.entities.create();
```

And destroying an entity is done with:
Expand All @@ -75,13 +77,13 @@ entity.destroy();
- Each `entityx::Entity` is a convenience class wrapping an `entityx::Entity::Id`.
- An `entityx::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 `entityx::Entity` handle.
- When an entity is created IDs are recycled from the free list before allocating new ones.
- When an entity is created IDs are recycled from the free list first, before allocating new ones.
- An `entityx::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.
- EntityX uses a reference counting smart pointer`entityx::ptr<T>` to manage object lifetimes. As a general rule, passing a pointer to any EntityX method will convert to a smart pointer and take ownership. To maintain your own reference to the pointer you will need to wrap it in `entityx::ptr<T>`.
- To improve cache coherence, components are constructed in contiguous memory ranges by using `entityx::EntityManager::assign<C>(id, ...)`. A light weight smart pointer (`ComponentPtr<C>`) is used to access the component.

### Components (entity data)

The general idea with the EntityX interpretation of ECS is to have as little functionality in components as possible. All logic should be contained in Systems.
The general idea with the EntityX interpretation of ECS is to have as little logic in components as possible. All logic should be contained in Systems.

To that end Components are typically [POD types](http://en.wikipedia.org/wiki/Plain_Old_Data_Structures) consisting of self-contained sets of related data. Implementations are [curiously recurring template pattern](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) (CRTP) subclasses of `entityx::Component<T>`.

Expand Down Expand Up @@ -112,21 +114,14 @@ To associate a component with a previously created entity call ``entityx::Entity
entity.assign<Position>(1.0f, 2.0f);
```

You can also assign existing instances of components:

```c++
entityx::ptr<Position> position(new Position(1.0f, 2.0f));
entity.assign(position);
```
#### Querying entities and their components

To query all entities with a set of components assigned, use ``entityx::EntityManager::entities_with_components()``. This method will return only those entities that have *all* of the specified components associated with them, assigning each component pointer to the corresponding component instance:

```c++
for (auto entity : entities->entities_with_components<Position, Direction>()) {
entityx::ptr<Position> position = entity.component<Position>();
entityx::ptr<Direction> direction = entity.component<Direction>();
for (Entity entity : entities.entities_with_components<Position, Direction>()) {
ComponentPtr<Position> position = entity.component<Position>();
ComponentPtr<Direction> direction = entity.component<Direction>();

// Do things with entity, position and direction.
}
Expand All @@ -135,7 +130,7 @@ for (auto entity : entities->entities_with_components<Position, Direction>()) {
To retrieve a component associated with an entity use ``entityx::Entity::component<C>()``:

```c++
entityx::ptr<Position> position = entity.component<Position>();
ComponentPtr<Position> position = entity.component<Position>();
if (position) {
// Do stuff with position
}
Expand Down Expand Up @@ -166,10 +161,10 @@ A basic movement system might be implemented with something like the following:

```c++
struct MovementSystem : public System<MovementSystem> {
void update(entityx::ptr<entityx::EntityManager> es, entityx::ptr<entityx::EventManager> events, double dt) override {
for (auto entity : es->entities_with_components<Position, Direction>()) {
entityx::ptr<Position> position = entity.component<Position>();
entityx::ptr<Direction> direction = entity.component<Direction>();
void update(entityx::EntityManager &es, entityx::EventManager &events, double dt) override {
for (Entity entity : es.entities_with_components<Position, Direction>()) {
ComponentPtr<Position> position = entity.component<Position>();
ComponentPtr<Direction> direction = entity.component<Direction>();

position->x += direction->x * dt;
position->y += direction->y * dt;
Expand Down Expand Up @@ -204,10 +199,10 @@ Next we implement our collision system, which emits ``Collision`` objects via an
```c++
class CollisionSystem : public System<CollisionSystem> {
public:
void update(entityx::ptr<entityx::EntityManager> es, entityx::ptr<entityx::EventManager> events, double dt) override {
entityx::ptr<Position> left_position, right_position;
for (auto left_entity : es->entities_with_components<Position>(left_position)) {
for (auto right_entity : es->entities_with_components<Position>(right_position)) {
void update(entityx::EntityManager &es, entityx::EventManager &events, double dt) override {
ComponentPtr<Position> left_position, right_position;
for (Entity left_entity : es.entities_with_components<Position>(left_position)) {
for (auto right_entity : es.entities_with_components<Position>(right_position)) {
if (collide(left_position, right_position)) {
events->emit<Collision>(left_entity, right_entity);
}
Expand All @@ -223,11 +218,11 @@ Objects interested in receiving collision information can subscribe to ``Collisi
```c++
struct DebugSystem : public System<DebugSystem>, Receiver<DebugSystem> {
void configure(entityx::ptr<entityx::EventManager> event_manager) {
event_manager->subscribe<Collision>(*this);
void configure(entityx::EventManager &event_manager) {
event_manager.subscribe<Collision>(*this);
}
void update(ptr<entityx::EntityManager> entities, ptr<entityx::EventManager> events, double dt) {}
void update(entityx::EntityManager &entities, entityx::EventManager &events, double dt) {}
void receive(const Collision &collision) {
LOG(DEBUG) << "entities collided: " << collision.left << " and " << collision.right << endl;
Expand All @@ -245,10 +240,10 @@ Several events are emitted by EntityX itself:
- `entityx::Entity entity` - entityx::Entity about to be destroyed.
- `ComponentAddedEvent<T>` - emitted when a new component is added to an entity.
- `entityx::Entity entity` - entityx::Entity that component was added to.
- `entityx::ptr<T> component` - The component added.
- `ComponentPtr<T> component` - The component added.
- `ComponentRemovedEvent<T>` - emitted when a component is removed from an entity.
- `entityx::Entity entity` - entityx::Entity that component was removed from.
- `entityx::ptr<T> component` - The component removed.
- `ComponentPtr<T> component` - The component removed.

#### Implementation notes

Expand Down
22 changes: 11 additions & 11 deletions entityx/Benchmarks_test.cc
Expand Up @@ -19,10 +19,10 @@ struct AutoTimer {

class BenchmarksTest : public ::testing::Test {
protected:
BenchmarksTest() : ev(EventManager::make()), em(EntityManager::make(ev)) {}
BenchmarksTest() : em(ev) {}

ptr<EventManager> ev;
ptr<EntityManager> em;
EventManager ev;
EntityManager em;
};


Expand All @@ -33,7 +33,7 @@ TEST_F(BenchmarksTest, TestCreateEntities) {
cout << "creating " << count << " entities" << endl;

for (uint64_t i = 0; i < count; i++) {
em->create();
em.create();
}
}

Expand All @@ -42,7 +42,7 @@ TEST_F(BenchmarksTest, TestDestroyEntities) {
uint64_t count = 10000000L;
vector<Entity> entities;
for (uint64_t i = 0; i < count; i++) {
entities.push_back(em->create());
entities.push_back(em.create());
}

AutoTimer t;
Expand All @@ -60,7 +60,7 @@ struct Listener : public Receiver<Listener> {

TEST_F(BenchmarksTest, TestCreateEntitiesWithListener) {
Listener listen;
ev->subscribe<EntityCreatedEvent>(listen);
ev.subscribe<EntityCreatedEvent>(listen);

uint64_t count = 10000000L;

Expand All @@ -69,18 +69,18 @@ TEST_F(BenchmarksTest, TestCreateEntitiesWithListener) {

vector<Entity> entities;
for (uint64_t i = 0; i < count; i++) {
entities.push_back(em->create());
entities.push_back(em.create());
}
}

TEST_F(BenchmarksTest, TestDestroyEntitiesWithListener) {
Listener listen;
ev->subscribe<EntityDestroyedEvent>(listen);
ev.subscribe<EntityDestroyedEvent>(listen);

uint64_t count = 10000000L;
vector<Entity> entities;
for (uint64_t i = 0; i < count; i++) {
entities.push_back(em->create());
entities.push_back(em.create());
}

AutoTimer t;
Expand All @@ -98,7 +98,7 @@ TEST_F(BenchmarksTest, TestEntityIteration) {
uint64_t count = 10000000L;
vector<Entity> entities;
for (uint64_t i = 0; i < count; i++) {
auto e = em->create();
auto e = em.create();
e.assign<Position>();
entities.push_back(e);
}
Expand All @@ -108,7 +108,7 @@ TEST_F(BenchmarksTest, TestEntityIteration) {

for (int i = 0; i < 10; ++i) {
ComponentPtr<Position> position;
for (auto e : em->entities_with_components<Position>(position)) {
for (auto e : em.entities_with_components<Position>(position)) {
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions entityx/Entity.cc
Expand Up @@ -18,20 +18,20 @@ BaseComponent::Family BaseComponent::family_counter_ = 0;

void Entity::invalidate() {
id_ = INVALID;
manager_.reset();
manager_ = nullptr;
}

void Entity::destroy() {
assert(valid());
manager_.lock()->destroy(id_);
manager_->destroy(id_);
invalidate();
}

std::bitset<entityx::MAX_COMPONENTS> Entity::component_mask() const {
return manager_.lock()->component_mask(id_);
return manager_->component_mask(id_);
}

EntityManager::EntityManager(ptr<EventManager> event_manager) : event_manager_(event_manager) {
EntityManager::EntityManager(EventManager &event_manager) : event_manager_(event_manager) {
}

EntityManager::~EntityManager() {
Expand Down

1 comment on commit f212094

@sansumbrella
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we be able to achieve the goals of contiguous memory by using std::shared_ptr with a custom allocator and deleter?

There are a handful of situations where I found the previous use of shared_ptr really useful.

Transform hierarchies:
I have a Transform component that stores an optional reference to a parent Transform. With shared_ptr, it doesn't matter if the entity (or other object) holding the parent transform is destroyed; the child transform's shared_ptr keeps the parent alive and the child maintains its correct total transform. With ComponentHandle, if the entity holding the parent transform is destroyed, the child transform's ComponentHandle will become invalid and the child will likely shift in space.

Pseudo-instancing:
Having the components as shared_ptr allowed for easily sharing the same component across entities.

Please sign in to comment.