diff options
author | Alec Thomas <alec@swapoff.org> | 2014-03-05 17:17:20 +1100 |
---|---|---|
committer | Alec Thomas <alec@swapoff.org> | 2014-03-05 17:57:52 +1100 |
commit | 715104854b63b5d1810d38c9e63251ba77b9fd91 (patch) | |
tree | 4edcd53ea0f14da13d94fe526fefe259b83192c0 | |
parent | ea005510200ed7851496c77d2c15e6d3de75ac7b (diff) |
Wrap component access in a ComponentHandle.
-rw-r--r-- | entityx/Benchmarks_test.cc | 2 | ||||
-rw-r--r-- | entityx/Entity.h | 233 | ||||
-rw-r--r-- | entityx/Entity_test.cc | 51 | ||||
-rw-r--r-- | entityx/ReferenceCounted.h | 0 | ||||
-rw-r--r-- | entityx/System_test.cc | 8 | ||||
-rw-r--r-- | entityx/help/Pool.h | 7 | ||||
-rw-r--r-- | entityx/help/Pool_test.cc | 16 | ||||
-rw-r--r-- | entityx/quick.h | 4 |
8 files changed, 237 insertions, 84 deletions
diff --git a/entityx/Benchmarks_test.cc b/entityx/Benchmarks_test.cc index d0aff02..cf431ab 100644 --- a/entityx/Benchmarks_test.cc +++ b/entityx/Benchmarks_test.cc @@ -108,8 +108,8 @@ TEST_CASE_METHOD(BenchmarkFixture, "TestEntityIteration") { AutoTimer t; cout << "iterating over " << count << " entities with a component 10 times" << endl; + ComponentHandle<Position> position; for (int i = 0; i < 10; ++i) { - Position *position; for (auto e : em.entities_with_components<Position>(position)) { } } diff --git a/entityx/Entity.h b/entityx/Entity.h index 2556caa..7362f69 100644 --- a/entityx/Entity.h +++ b/entityx/Entity.h @@ -37,6 +37,9 @@ namespace entityx { class EntityManager; +template <typename C> +class ComponentHandle; + /** A convenience handle around an Entity::Id. * * If an entity is destroyed, any copies will be invalidated. Use valid() to @@ -120,16 +123,19 @@ public: Id id() const { return id_; } template <typename C, typename ... Args> - C *assign(Args && ... args); + ComponentHandle<C> assign(Args && ... args); template <typename C> void remove(); template <typename C> - C *component(); + ComponentHandle<C> component(); + + template <typename C> + bool has_component() const; template <typename A, typename ... Args> - void unpack(A *&a, Args *& ... args); + void unpack(ComponentHandle<A> &a, ComponentHandle<Args> & ... args); /** * Destroy and invalidate this Entity. @@ -144,16 +150,53 @@ public: }; -inline std::ostream &operator << (std::ostream &out, const Entity::Id &id) { - out << "Entity::Id(" << id.index() << "." << id.version() << ")"; - return out; -} +/** + * A ComponentHandle<C> is a wrapper around an instance of a component. + * + * It provides safe access to components. The handle will be invalidated under + * the following conditions: + * + * - If a component is removed from its host entity. + * - If its host entity is destroyed. + */ +template <typename C> +class ComponentHandle { +public: + ComponentHandle() : manager_(nullptr) {} + bool valid() const; + operator bool() const; -inline std::ostream &operator << (std::ostream &out, const Entity &entity) { - out << "Entity(" << entity.id() << ")"; - return out; -} + C *operator -> (); + const C *operator -> () const; + + C *get(); + const C *get() const; + + /** + * Remove the component from its entity and destroy it. + */ + void remove(); + + bool operator == (const ComponentHandle<C> &other) const { + return manager_ == other.manager_ && id_ == other.id_; + } + + bool operator != (const ComponentHandle<C> &other) const { + return !(*this == other); + } + +private: + friend class EntityManager; + + ComponentHandle(EntityManager *manager, Entity::Id id) : + manager_(manager), id_(id) {} + ComponentHandle(const EntityManager *manager, Entity::Id id) : + manager_(const_cast<EntityManager*>(manager)), id_(id) {} + + EntityManager *manager_; + Entity::Id id_; +}; /** @@ -227,11 +270,11 @@ struct EntityDestroyedEvent : public Event<EntityDestroyedEvent> { */ template <typename C> struct ComponentAddedEvent : public Event<ComponentAddedEvent<C>> { - ComponentAddedEvent(Entity entity, C *component) : + ComponentAddedEvent(Entity entity, ComponentHandle<C> component) : entity(entity), component(component) {} Entity entity; - C *component; + ComponentHandle<C> component; }; /** @@ -239,11 +282,11 @@ struct ComponentAddedEvent : public Event<ComponentAddedEvent<C>> { */ template <typename C> struct ComponentRemovedEvent : public Event<ComponentRemovedEvent<C>> { - ComponentRemovedEvent(Entity entity, C *component) : + ComponentRemovedEvent(Entity entity, ComponentHandle<C> component) : entity(entity), component(component) {} Entity entity; - C *component; + ComponentHandle<C> component; }; @@ -331,13 +374,13 @@ class EntityManager : entityx::help::NonCopyable { const Iterator end() const { return Iterator(manager_, predicates_, unpackers_, manager_->capacity()); } template <typename A> - View &unpack_to(A *&a) { + View &unpack_to(ComponentHandle<A> &a) { unpackers_.push_back(std::shared_ptr<Unpacker<A>>(new Unpacker<A>(manager_, a))); return *this; } template <typename A, typename B, typename ... Args> - View &unpack_to(A *&a, B *&b, Args *& ... args) { + View &unpack_to(ComponentHandle<A> &a, ComponentHandle<B> &b, ComponentHandle<Args> & ... args) { unpack_to<A>(a); return unpack_to<B, Args ...>(b, args ...); } @@ -347,7 +390,7 @@ class EntityManager : entityx::help::NonCopyable { template <typename C> struct Unpacker : BaseUnpacker { - Unpacker(EntityManager *manager, C *&c) : manager_(manager), c(c) {} + Unpacker(EntityManager *manager, ComponentHandle<C> &c) : manager_(manager), c(c) {} void unpack(const Entity::Id &id) { c = manager_->component<C>(id); @@ -355,7 +398,7 @@ class EntityManager : entityx::help::NonCopyable { private: EntityManager *manager_; - C *&c; + ComponentHandle<C> &c; }; View(EntityManager *manager, Predicate predicate) : manager_(manager) { @@ -445,12 +488,13 @@ class EntityManager : entityx::help::NonCopyable { * @returns Smart pointer to newly created component. */ template <typename C, typename ... Args> - C *assign(Entity::Id id, Args && ... args) { + ComponentHandle<C> assign(Entity::Id id, Args && ... args) { assert_valid(id); const int family = C::family(); // Placement new into the component pool. Pool<C> *pool = accomodate_component<C>(); - C *component = new(pool->get(id.index())) C(std::forward<Args>(args) ...); + new(pool->get(id.index())) C(std::forward<Args>(args) ...); + ComponentHandle<C> component(this, id); entity_component_mask_[id.index()] |= uint64_t(1) << family; event_manager_.emit<ComponentAddedEvent<C>>(Entity(this, id), component); return component; @@ -466,29 +510,45 @@ class EntityManager : entityx::help::NonCopyable { assert_valid(id); const int family = C::family(); const int index = id.index(); + ComponentHandle<C> component(this, id); BasePool *pool = component_pools_[family]; - C *component = static_cast<C*>(pool->get(id.index())); - entity_component_mask_[id.index()] &= ~(uint64_t(1) << family); event_manager_.emit<ComponentRemovedEvent<C>>(Entity(this, id), component); + entity_component_mask_[id.index()] &= ~(uint64_t(1) << family); pool->destroy(index); } /** + * Check if an Entity has a component. + */ + template <typename C> + bool has_component(Entity::Id id) const { + assert_valid(id); + size_t family = C::family(); + // We don't bother checking the component mask, as we return a nullptr anyway. + if (family >= component_pools_.size()) + return false; + BasePool *pool = component_pools_[family]; + if (!pool || !entity_component_mask_[id.index()][family]) + return false; + return true; + } + + /** * Retrieve a Component assigned to an Entity::Id. * * @returns Pointer to an instance of C, or nullptr if the Entity::Id does not have that Component. */ template <typename C> - C *component(Entity::Id id) { + ComponentHandle<C> component(Entity::Id id) { assert_valid(id); size_t family = C::family(); // We don't bother checking the component mask, as we return a nullptr anyway. if (family >= component_pools_.size()) - return nullptr; + return ComponentHandle<C>(); BasePool *pool = component_pools_[family]; if (!pool || !entity_component_mask_[id.index()][family]) - return nullptr; - return static_cast<C*>(pool->get(id.index())); + return ComponentHandle<C>(); + return ComponentHandle<C>(this, id); } /** @@ -497,16 +557,16 @@ class EntityManager : entityx::help::NonCopyable { * @returns Component instance, or nullptr if the Entity::Id does not have that Component. */ template <typename C> - const C *component(Entity::Id id) const { + const ComponentHandle<const C> component(Entity::Id id) const { assert_valid(id); size_t family = C::family(); // We don't bother checking the component mask, as we return a nullptr anyway. if (family >= component_pools_.size()) - return nullptr; + return ComponentHandle<const C>(); BasePool *pool = component_pools_[family]; if (!pool || !entity_component_mask_[id.index()][family]) - return nullptr; - return static_cast<const C*>(pool->get(id.index())); + return ComponentHandle<const C>(); + return ComponentHandle<const C>(this, id); } /** @@ -514,8 +574,8 @@ class EntityManager : entityx::help::NonCopyable { * * @code * for (Entity entity : entity_manager.entities_with_components<Position, Direction>()) { - * Position *position = entity.component<Position>(); - * Direction *direction = entity.component<Direction>(); + * ComponentHandle<Position> position = entity.component<Position>(); + * ComponentHandle<Direction> direction = entity.component<Direction>(); * * ... * } @@ -527,30 +587,38 @@ class EntityManager : entityx::help::NonCopyable { return View(this, ComponentMaskPredicate(entity_component_mask_, mask)); } + template <typename C> + View entities_with_components(ComponentHandle<C> &c) { + auto mask = component_mask<C>(); + return + View(this, ComponentMaskPredicate(entity_component_mask_, mask)) + .unpack_to(c); + } + /** * Find Entities that have all of the specified Components and assign them * to the given parameters. * * @code - * Position *position; - * Direction *direction; + * ComponentHandle<Position> position; + * ComponentHandle<Direction> direction; * for (Entity entity : entity_manager.entities_with_components(position, direction)) { * // Use position and component here. * } * @endcode */ template <typename C, typename ... Components> - View entities_with_components(C *&c, Components *& ... args) { - auto mask = component_mask(c, args ...); + View entities_with_components(ComponentHandle<C> &c, ComponentHandle<Components> & ... args) { + auto mask = component_mask<C, Components...>(); return View(this, ComponentMaskPredicate(entity_component_mask_, mask)) .unpack_to(c, args ...); } - template <typename A> - void unpack(Entity::Id id, A *&a) { + template <typename C> + void unpack(Entity::Id id, ComponentHandle<C> &a) { assert_valid(id); - a = component<A>(id); + a = component<C>(id); } /** @@ -560,12 +628,12 @@ class EntityManager : entityx::help::NonCopyable { * * Useful for fast bulk iterations. * - * ComponentPtr<Position> p; - * ComponentPtr<Direction> d; + * ComponentHandle<Position> p; + * ComponentHandle<Direction> d; * unpack<Position, Direction>(e, p, d); */ template <typename A, typename ... Args> - void unpack(Entity::Id id, A *&a, Args *& ... args) { + void unpack(Entity::Id id, ComponentHandle<A> &a, ComponentHandle<Args> & ... args) { assert_valid(id); a = component<A>(id); unpack<Args ...>(id, args ...); @@ -578,6 +646,8 @@ class EntityManager : entityx::help::NonCopyable { private: friend class Entity; + template <typename C> + friend class ComponentHandle; /// A predicate that matches valid entities with the given component mask. class ComponentMaskPredicate { @@ -634,13 +704,13 @@ class EntityManager : entityx::help::NonCopyable { } template <typename C> - ComponentMask component_mask(const C *c) { + ComponentMask component_mask(const ComponentHandle<C> c) { return component_mask<C>(); } - template <typename C1, typename C2, typename ... Components> - ComponentMask component_mask(const C1 *c1, const C2 *c2, Components * ... args) { - return component_mask<C1>(c1) | component_mask<C2, Components ...>(c2, args...); + template <typename C1, typename ... Components> + ComponentMask component_mask(const ComponentHandle<C1> &c1, const ComponentHandle<Components> &... args) { + return component_mask<C1, Components ...>(); } inline void accomodate_entity(uint32_t index) { @@ -670,7 +740,8 @@ class EntityManager : entityx::help::NonCopyable { uint32_t index_counter_ = 0; EventManager &event_manager_; - // Each element in component_pools_ corresponds to a Component::family(). + // Each element in component_pools_ corresponds to a Pool for a Component. + // The index into the vector is the Component::family(). std::vector<BasePool*> component_pools_; // Bitmask of components associated with each entity. Index into the vector is the Entity::Id. std::vector<ComponentMask> entity_component_mask_; @@ -690,25 +761,31 @@ BaseComponent::Family Component<C>::family() { template <typename C, typename ... Args> -C *Entity::assign(Args && ... args) { +ComponentHandle<C> Entity::assign(Args && ... args) { assert(valid()); return manager_->assign<C>(id_, std::forward<Args>(args) ...); } template <typename C> void Entity::remove() { - assert(valid() && component<C>()); + assert(valid() && has_component<C>()); manager_->remove<C>(id_); } template <typename C> -C *Entity::component() { +ComponentHandle<C> Entity::component() { assert(valid()); return manager_->component<C>(id_); } +template <typename C> +bool Entity::has_component() const { + assert(valid()); + return manager_->has_component<C>(id_); +} + template <typename A, typename ... Args> -void Entity::unpack(A *&a, Args *& ... args) { +void Entity::unpack(ComponentHandle<A> &a, ComponentHandle<Args> & ... args) { assert(valid()); manager_->unpack(id_, a, args ...); } @@ -717,5 +794,57 @@ inline bool Entity::valid() const { return manager_ && manager_->valid(id_); } +inline std::ostream &operator << (std::ostream &out, const Entity::Id &id) { + out << "Entity::Id(" << id.index() << "." << id.version() << ")"; + return out; +} + + +inline std::ostream &operator << (std::ostream &out, const Entity &entity) { + out << "Entity(" << entity.id() << ")"; + return out; +} + + +template <typename C> +inline ComponentHandle<C>::operator bool() const { + return valid(); +} + +template <typename C> +inline bool ComponentHandle<C>::valid() const { + return manager_ && manager_->valid(id_) && manager_->has_component<C>(id_); +} + +template <typename C> +inline C *ComponentHandle<C>::operator -> () { + assert(valid()); + return manager_->get_component_ptr<C>(id_); +} + +template <typename C> +inline const C *ComponentHandle<C>::operator -> () const { + assert(valid()); + return manager_->get_component_ptr<C>(id_); +} + +template <typename C> +inline C *ComponentHandle<C>::get() { + assert(valid()); + return manager_->get_component_ptr<C>(id_); +} + +template <typename C> +inline const C *ComponentHandle<C>::get() const { + assert(valid()); + return manager_->get_component_ptr<C>(id_); +} + +template <typename C> +inline void ComponentHandle<C>::remove() { + assert(valid()); + manager_->remove<C>(id_); +} + } // namespace entityx diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc index db7ef18..ea9971a 100644 --- a/entityx/Entity_test.cc +++ b/entityx/Entity_test.cc @@ -197,7 +197,7 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponentAndUnpacking Entity e = em.create(); Entity f = em.create(); Entity g = em.create(); - std::vector<std::pair<Position *, Direction *>> position_directions; + std::vector<std::pair<ComponentHandle<Position>, ComponentHandle<Direction>>> position_directions; position_directions.push_back(std::make_pair( e.assign<Position>(1.0f, 2.0f), e.assign<Direction>(3.0f, 4.0f))); position_directions.push_back(std::make_pair( @@ -206,21 +206,21 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestGetEntitiesWithComponentAndUnpacking g.assign<Position>(5.0f, 6.0f); int i = 0; - Position *position; + ComponentHandle<Position> position; REQUIRE(3 == size(em.entities_with_components(position))); - Direction *direction; + ComponentHandle<Direction> direction; for (auto unused_entity : em.entities_with_components(position, direction)) { (void)unused_entity; - REQUIRE(static_cast<bool>(position)); - REQUIRE(static_cast<bool>(direction)); + REQUIRE(position); + REQUIRE(direction); auto pd = position_directions.at(i); REQUIRE(position == pd.first); REQUIRE(direction == pd.second); ++i; } REQUIRE(2 == i); - Tag *tag; + ComponentHandle<Tag> tag; i = 0; for (auto unused_entity : em.entities_with_components(position, direction, tag)) { @@ -243,9 +243,9 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestUnpack") { auto d = e.assign<Direction>(3.0, 4.0); auto t = e.assign<Tag>("tag"); - Position *up; - Direction *ud; - Tag *ut; + ComponentHandle<Position> up; + ComponentHandle<Direction> ud; + ComponentHandle<Tag> ut; e.unpack(up); REQUIRE(p == up); e.unpack(up, ud); @@ -368,7 +368,7 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestComponentRemovedEvent") { removed = event.component; } - Direction *removed = nullptr; + ComponentHandle<Direction> removed; }; ComponentRemovedReceiver receiver; @@ -376,7 +376,7 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestComponentRemovedEvent") { REQUIRE(!(receiver.removed)); Entity e = em.create(); - Direction *p = e.assign<Direction>(1.0, 2.0); + ComponentHandle<Direction> p = e.assign<Direction>(1.0, 2.0); e.remove<Direction>(); REQUIRE(receiver.removed == p); REQUIRE(!(e.component<Direction>())); @@ -421,7 +421,30 @@ TEST_CASE_METHOD(EntityManagerFixture, "TestEntityDestroyHole") { REQUIRE(count() == 4999); } -TEST_CASE_METHOD(EntityManagerFixture, "DeleteComponentThrowsBadAlloc") { - Position *position = new Position(); - REQUIRE_THROWS_AS(delete position, std::bad_alloc); +// TODO(alec): Disable this on OSX - it doesn't seem to be possible to catch it?!? +// TEST_CASE_METHOD(EntityManagerFixture, "DeleteComponentThrowsBadAlloc") { +// Position *position = new Position(); +// REQUIRE_THROWS_AS(delete position, std::bad_alloc); +// } + + +TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenEntityDestroyed") { + Entity a = em.create(); + ComponentHandle<Position> position = a.assign<Position>(1, 2); + REQUIRE(position); + REQUIRE(position->x == 1); + REQUIRE(position->y == 2); + a.destroy(); + REQUIRE(!position); +} + + +TEST_CASE_METHOD(EntityManagerFixture, "TestComponentHandleInvalidatedWhenComponentDestroyed") { + Entity a = em.create(); + ComponentHandle<Position> position = a.assign<Position>(1, 2); + REQUIRE(position); + REQUIRE(position->x == 1); + REQUIRE(position->y == 2); + a.remove<Position>(); + REQUIRE(!position); } diff --git a/entityx/ReferenceCounted.h b/entityx/ReferenceCounted.h deleted file mode 100644 index e69de29..0000000 --- a/entityx/ReferenceCounted.h +++ /dev/null diff --git a/entityx/System_test.cc b/entityx/System_test.cc index 9253003..746a1ac 100644 --- a/entityx/System_test.cc +++ b/entityx/System_test.cc @@ -39,8 +39,8 @@ class MovementSystem : public System<MovementSystem> { void update(EntityManager &es, EventManager &events, double) override { EntityManager::View entities = es.entities_with_components<Position, Direction>(); - Position *position; - Direction *direction; + ComponentHandle<Position> position; + ComponentHandle<Direction> direction; for (auto entity : entities) { entity.unpack<Position, Direction>(position, direction); position->x += direction->x; @@ -77,8 +77,8 @@ TEST_CASE_METHOD(EntitiesFixture, "TestApplySystem") { systems.configure(); systems.update<MovementSystem>(0.0); - Position *position; - Direction *direction; + ComponentHandle<Position> position; + ComponentHandle<Direction> direction; for (auto entity : created_entities) { entity.unpack<Position, Direction>(position, direction); if (position && direction) { diff --git a/entityx/help/Pool.h b/entityx/help/Pool.h index f7b33d5..ac959d2 100644 --- a/entityx/help/Pool.h +++ b/entityx/help/Pool.h @@ -1,5 +1,3 @@ -#pragma once - /* * Copyright (C) 2012-2014 Alec Thomas <alec@swapoff.org> * All rights reserved. @@ -10,6 +8,8 @@ * Author: Alec Thomas <alec@swapoff.org> */ +#pragma once + #include <cassert> #include <vector> @@ -21,6 +21,9 @@ namespace entityx { * destroyed. * * The semi-contiguous nature aims to provide cache-friendly iteration. + * + * Lookups are O(1). + * Appends are amortized O(1). */ class BasePool { public: diff --git a/entityx/help/Pool_test.cc b/entityx/help/Pool_test.cc index f04bc29..56ca85a 100644 --- a/entityx/help/Pool_test.cc +++ b/entityx/help/Pool_test.cc @@ -56,14 +56,14 @@ TEST_CASE("TestPoolPointers") { char *p16 = static_cast<char*>(pool.get(16)); char *p24 = static_cast<char*>(pool.get(24)); - char *expected_p7 = p0 + 7 * sizeof(Position); - REQUIRE(expected_p7 == p7); - char *extrapolated_p8 = p0 + 8 * sizeof(Position); - REQUIRE(extrapolated_p8 != p8); - char *extrapolated_p16 = p8 + 8 * sizeof(Position); - REQUIRE(extrapolated_p16 != p16); - char *extrapolated_p24 = p16 + 8 * sizeof(Position); - REQUIRE(extrapolated_p24 != p24); + void *expected_p7 = p0 + 7 * sizeof(Position); + REQUIRE(expected_p7 == static_cast<void*>(p7)); + void *extrapolated_p8 = p0 + 8 * sizeof(Position); + REQUIRE(extrapolated_p8 != static_cast<void*>(p8)); + void *extrapolated_p16 = p8 + 8 * sizeof(Position); + REQUIRE(extrapolated_p16 != static_cast<void*>(p16)); + void *extrapolated_p24 = p16 + 8 * sizeof(Position); + REQUIRE(extrapolated_p24 != static_cast<void*>(p24)); } TEST_CASE("TestDeconstruct") { diff --git a/entityx/quick.h b/entityx/quick.h index d52656b..9a668eb 100644 --- a/entityx/quick.h +++ b/entityx/quick.h @@ -10,10 +10,8 @@ #pragma once - #include "entityx/config.h" - namespace entityx { /** @@ -21,7 +19,7 @@ namespace entityx { * SystemManager. */ class EntityX { -public: + public: EntityX() : entities(events), systems(entities, events) {} EventManager events; |