diff options
author | Alec Thomas <alec@swapoff.org> | 2012-10-22 14:16:43 -0400 |
---|---|---|
committer | Alec Thomas <alec@swapoff.org> | 2012-10-22 14:16:43 -0400 |
commit | b547516621b71bf635f04c8e7ab52b86b1480771 (patch) | |
tree | c1b8a9e0679e7c87fd3b14c4e7312dc6a6b79931 | |
parent | ec441e05616b3e9a5a384236b8c0ff536ed859e5 (diff) |
Convert remaining bare pointers to shared_ptr.
-rw-r--r-- | README.md | 32 | ||||
-rw-r--r-- | entityx/Entity.h | 33 | ||||
-rw-r--r-- | entityx/Entity_test.cc | 44 |
3 files changed, 67 insertions, 42 deletions
@@ -1,18 +1,20 @@ # EntityX - A fast, type-safe C++ Entity-Component system -Entity-Component (EC) systems decouple entity behavior from the entity objects themselves. The [Evolve your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) article provides a solid overview of EC systems. +Entity-Component (EC) systems are a form of decomposition that completely decouple entity logic and data from the entity "objects" themselves. The [Evolve your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) article provides a solid overview of EC systems. EntityX is an EC system that uses C++11 features to provide type-safe component management, event delivery, etc. ## Overview -In EntityX, data is associated with entities via components. Systems then use component data to implement behavior. Systems can utilize as many components as necessary. As an example, a physics system might need *position* and *mass* data, while a collision system might only need *position* - the data would be logically separated, but usable by any system. +In EntityX data associated with an entity is called a `Component`. `Systems` use components to implement behavior and can utilize as many components as necessary. An `EventManager` allows systems to interact without being tightly coupled. Finally, a `World` object ties all of the systems together for convenience. -Finally, an event system ties systems together, allowing them to interact without being tightly coupled. +As an example, a physics system might need *position* and *mass* data, while a collision system might only need *position* - the data would be logically separated into two components, but usable by any system. The physics system might emit *collision* events whenever two entities collide. + +## Tutorial ### Entities -Entities are simply 64-bit numeric identifiers with which component data is associated. Entity IDs are allocated by the `EntityManager`. Data can then be associated with an entity, and queried or retrieved directly. +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. Creating an entity is as simple as: @@ -60,7 +62,7 @@ boost::shared_ptr<Position> position = boost::make_shared<Position>(1.0f, 2.0f); entities.assign(entity, position); ``` -#### Querying entities and components +#### Querying entities and their components To retrieve a component associated with an entity use ``EntityManager::component()``: @@ -71,11 +73,11 @@ if (position) { } ``` -To query all components with a set of components assigned use ``EntityManager::entities_with_components()``. This method will return only those entities that have *all* of the specified components associated with them, assigning the component pointer to the corresponding component pointer: +To query all components with a set of components assigned use ``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: ``` -Position *position; -Direction *direction; +boost::shared_ptr<Position> position; +boost::shared_ptr<Direction> direction; for (auto entity : entities.entities_with_components(position, direction)) { // Do things with entity ID, position and direction. } @@ -83,7 +85,7 @@ for (auto entity : entities.entities_with_components(position, direction)) { ### Systems (implementing behavior) -Systems implement behavior using one or more components. Implementations are [CRTP](http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) subclasses of `System<T>` and *must* implement the `update()` method, as shown below. +Systems implement behavior using one or more components. Implementations are subclasses of `System<T>` and *must* implement the `update()` method, as shown below. A basic movement system might be implemented with something like the following: @@ -106,6 +108,8 @@ Events are objects emitted by systems, typically when some condition is met. Lis As an example, we might want to implement a very basic collision system using our ``Position` data from above. +#### Creating event types + First, we define the event type, which for our example is simply the two entities that collided: ``` @@ -116,13 +120,15 @@ struct Collision : public Event<Collision> { }; ``` +#### Emitting events + Next we implement our collision system, which emits ``Collision`` objects via an ``EventManager`` instance whenever two entities collide. ``` class CollisionSystem : public System<CollisionSystem> { public: void update(EntityManager &es, EventManager &events, double dt) override { - Position *left_position, *right_position; + boost::shared_ptr<Position> left_position, right_position; for (auto left_entity : es.entities_with_components(left_position)) { for (auto right_entity : es.entities_with_components(right_position)) { if (collide(left_position, right_position)) { @@ -134,7 +140,9 @@ class CollisionSystem : public System<CollisionSystem> { }; ``` -Objects interested in receiving collision can subscribe to ``Collision`` events by first subclassing the CRTP class ``Receiver<T>``: +#### Subscribing to events + +Objects interested in receiving collision information can subscribe to ``Collision`` events by first subclassing the CRTP class ``Receiver<T>``: ``` struct DebugCollisions : public Receiver<DebugCollisions> { @@ -144,6 +152,8 @@ struct DebugCollisions : public Receiver<DebugCollisions> { }; ``` +Note that a single class can receive any number of types of events by implementing a ``receive(const EventType &)`` method for each event type. + Finally, we subscribe our receiver to collision events: ``` diff --git a/entityx/Entity.h b/entityx/Entity.h index 6c0ab55..356c773 100644 --- a/entityx/Entity.h +++ b/entityx/Entity.h @@ -127,7 +127,7 @@ struct ComponentAddedEvent : public Event<ComponentAddedEvent<T>> { * e.assign<Movable>(player); * e.assign<Physical>(player); * e.assign<Scriptable>(player); - * Controllable *controllable = e.assign<Controllable>(player); + * shared_ptr<Controllable> controllable = e.assign<Controllable>(player); */ class EntityManager : boost::noncopyable { private: @@ -216,19 +216,18 @@ class EntityManager : boost::noncopyable { const Iterator begin() const { return Iterator(manager_, predicates_, unpackers_, 0); } const Iterator end() const { return Iterator(manager_, predicates_, unpackers_, manager_.size()); } - // It's a bit less than ideal to mix this int othe View, but I couldn't find a way to separate concerns without - // vastly increasing the amount of code that had to be written. template <typename A> - void unpack_to(A *&a) { + View &unpack_to(boost::shared_ptr<A> &a) { unpackers_.push_back([&] (Entity id) { - a = manager_.component<A>(id).get(); + a = manager_.component<A>(id); }); + return *this; } template <typename A, typename B, typename ... Args> - void unpack_to(A *&a, B *&b, Args *& ... args) { + View &unpack_to(boost::shared_ptr<A> &a, boost::shared_ptr<B> &b, Args *& ... args) { unpack_to<A>(a); - unpack_to<B, Args ...>(b, args ...); + return unpack_to<B, Args ...>(b, args ...); } private: friend class EntityManager; @@ -308,7 +307,7 @@ class EntityManager : boost::noncopyable { /** * Assign a Component to an Entity, optionally passing through Component constructor arguments. * - * Position *position = em.assign<Position>(e, x, y); + * shared_ptr<Position> position = em.assign<Position>(e, x, y); * * @returns Newly created component. */ @@ -328,7 +327,7 @@ class EntityManager : boost::noncopyable { if (C::family() >= entity_components_.size()) { return boost::shared_ptr<C>(); } - auto &c = entity_components_.at(C::family()).at(id); + boost::shared_ptr<BaseComponent> c = entity_components_.at(C::family()).at(id); return boost::static_pointer_cast<C>(c); } @@ -354,22 +353,22 @@ class EntityManager : boost::noncopyable { * Get all entities with the given component. */ template <typename C> - View entities_with_components(C *&c) { + View entities_with_components(boost::shared_ptr<C> &c) { auto mask = component_mask<C>(); - auto view = View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask)); - view.unpack_to<C>(c); - return view; + return + View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask)) + .unpack_to<C>(c); } /** * Find Entities that have all of the specified Components. */ template <typename C1, typename C2, typename ... Components> - View entities_with_components(C1 *&c1, C2 *&c2, Components *& ... args) { + View entities_with_components(boost::shared_ptr<C1> &c1, boost::shared_ptr<C2> &c2, Components *& ... args) { auto mask = component_mask<C1, C2, Components ...>(); - auto view = View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask)); - view.unpack_to<C1, C2, Components...>(c1, c2, args...); - return view; + return + View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask)) + .unpack_to<C1, C2, Components...>(c1, c2, args...); } /** diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc index 33f3773..aa1b32b 100644 --- a/entityx/Entity_test.cc +++ b/entityx/Entity_test.cc @@ -159,20 +159,20 @@ TEST_F(EntityManagerTest, TestGetEntitiesWithComponentAndUnpacking) { position_directions.push_back(std::make_pair( em.assign<Position>(e, 1.0f, 2.0f), em.assign<Direction>(e, 3.0f, 4.0f))); - em.assign<Position>(f, 5.0f, 6.0f); position_directions.push_back(std::make_pair( - em.assign<Position>(g, 7.0f, 8.0f), - em.assign<Direction>(g, 9.0f, 10.0f))); - Position *position; - Direction *direction; + em.assign<Position>(f, 7.0f, 8.0f), + em.assign<Direction>(f, 9.0f, 10.0f))); + em.assign<Position>(g, 5.0f, 6.0f); int i = 0; + shared_ptr<Position> position; + shared_ptr<Direction> direction; for (auto unused_entity : em.entities_with_components(position, direction)) { (void)unused_entity; - ASSERT_TRUE(position != nullptr); - ASSERT_TRUE(direction != nullptr); + ASSERT_TRUE(position); + ASSERT_TRUE(direction); auto pd = position_directions.at(i); - ASSERT_EQ(*position, *pd.first); - ASSERT_EQ(*direction, *pd.second); + ASSERT_EQ(position, pd.first); + ASSERT_EQ(direction, pd.second); ++i; } ASSERT_EQ(2, i); @@ -253,22 +253,38 @@ TEST_F(EntityManagerTest, TestComponentAddedEvent) { struct ComponentAddedEventReceiver : public Receiver<ComponentAddedEventReceiver> { void receive(const ComponentAddedEvent<Position> &event) { auto p = event.component; - float n = float(created.size()); + float n = float(position_events); ASSERT_EQ(p->x, n); ASSERT_EQ(p->y, n); - created.push_back(event.entity); + position_events++; } - vector<Entity> created; + void receive(const ComponentAddedEvent<Direction> &event) { + auto p = event.component; + float n = float(direction_events); + ASSERT_EQ(p->x, -n); + ASSERT_EQ(p->y, -n); + direction_events++; + } + + int position_events = 0; + int direction_events = 0; }; ComponentAddedEventReceiver receiver; ev.subscribe<ComponentAddedEvent<Position>>(receiver); + ev.subscribe<ComponentAddedEvent<Direction>>(receiver); - ASSERT_EQ(0, receiver.created.size()); + ASSERT_NE(ComponentAddedEvent<Position>::family(), + ComponentAddedEvent<Direction>::family()); + + ASSERT_EQ(0, receiver.position_events); + ASSERT_EQ(0, receiver.direction_events); for (int i = 0; i < 10; ++i) { Entity e = em.create(); em.assign<Position>(e, float(i), float(i)); + em.assign<Direction>(e, float(-i), float(-i)); } - ASSERT_EQ(10, receiver.created.size()); + ASSERT_EQ(10, receiver.position_events); + ASSERT_EQ(10, receiver.direction_events); }; |