aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlec Thomas <alec@swapoff.org>2012-09-20 21:24:08 -0400
committerAlec Thomas <alec@swapoff.org>2012-09-20 21:24:08 -0400
commitd98de9aeb12ff50d49b833439bd78402e85d1991 (patch)
tree4e8289ca39d2fd5265e092ba0f8e771d5998d529
Initial import.
-rw-r--r--.gitignore4
-rw-r--r--Makefile21
-rw-r--r--Makefile.conf9
-rw-r--r--entityx/Components.cc11
-rw-r--r--entityx/Components.h66
-rw-r--r--entityx/Components_test.cc54
-rw-r--r--entityx/Entity.cc18
-rw-r--r--entityx/Entity.h419
-rw-r--r--entityx/Entity_test.cc207
-rw-r--r--entityx/Event.cc17
-rw-r--r--entityx/Event.h153
-rw-r--r--entityx/Event_test.cc40
-rw-r--r--entityx/Makefile66
-rw-r--r--entityx/System.cc24
-rw-r--r--entityx/System.h128
-rw-r--r--entityx/System_test.cc117
-rw-r--r--entityx/World.cc35
-rw-r--r--entityx/World.h59
18 files changed, 1448 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dc667ff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.*.dep
+*.a
+*.so
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4a8e416
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+PREFIX=/usr/local
+
+all:
+ @make -C entityx all
+
+test:
+ @make -C entityx test
+
+clean:
+ @make -C entityx clean
+
+cleantests:
+ @make -C entityx cleantests
+
+distclean:
+ @make -C entityx distclean
+
+install: all
+ install -m755 ./entityx/libentity.so $(PREFIX)/lib
+ install -m644 ./entityx/libentity.a $(PREFIX)/lib
+ install -m644 ./entityx/*.h $(PREFIX)/include
diff --git a/Makefile.conf b/Makefile.conf
new file mode 100644
index 0000000..49ba7e3
--- /dev/null
+++ b/Makefile.conf
@@ -0,0 +1,9 @@
+CXX=c++
+DEBUG=-g
+CXXFLAGS=-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/entityx/Components.cc b/entityx/Components.cc
new file mode 100644
index 0000000..0103144
--- /dev/null
+++ b/entityx/Components.cc
@@ -0,0 +1,11 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include "Components.h"
diff --git a/entityx/Components.h b/entityx/Components.h
new file mode 100644
index 0000000..8c96fa7
--- /dev/null
+++ b/entityx/Components.h
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#pragma once
+
+#include <string>
+#include <boost/unordered_set.hpp>
+#include "entityx/Entity.h"
+
+namespace entity {
+
+/**
+ * Allow entities to be tagged with strings.
+ */
+class TagsComponent : public Component<TagsComponent> {
+ struct TagsPredicate {
+ TagsPredicate(const std::string &tag) : tag(tag) {}
+
+ bool operator () (EntityManager &manager, Entity id) {
+ auto tags = manager.component<TagsComponent>(id);
+ return tags != nullptr && tags->tags.find(tag) != tags->tags.end();
+ }
+
+ std::string tag;
+ };
+
+ public:
+ /**
+ * Construct a new TagsComponent with the given tags.
+ *
+ * eg. TagsComponent tags("a", "b", "c");
+ */
+ template <typename ... Args>
+ TagsComponent(const std::string &tag, const Args & ... tags) {
+ set_tags(tag, tags ...);
+ }
+
+ /**
+ * Filter the provided view to only those entities with the given tag.
+ */
+ static EntityManager::View view(const EntityManager::View &view, const std::string &tag) {
+ return EntityManager::View(view, TagsPredicate(tag));
+ }
+
+ boost::unordered_set<std::string> tags;
+
+ private:
+ template <typename ... Args>
+ void set_tags(const std::string &tag1, const std::string &tag2, const Args & ... tags) {
+ this->tags.insert(tag1);
+ set_tags(tag2, tags ...);
+ }
+
+ void set_tags(const std::string &tag) {
+ tags.insert(tag);
+ }
+};
+
+}
diff --git a/entityx/Components_test.cc b/entityx/Components_test.cc
new file mode 100644
index 0000000..e32fb46
--- /dev/null
+++ b/entityx/Components_test.cc
@@ -0,0 +1,54 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include <gtest/gtest.h>
+#include "entityx/Components.h"
+
+using namespace std;
+using namespace boost;
+using namespace entity;
+
+
+struct Position : public Component<Position> {};
+
+
+template <typename T>
+int size(const T &t) {
+ int n = 0;
+ for (auto i : t) {
+ ++n;
+ }
+ return n;
+}
+
+
+TEST(TagsComponentTest, TestVariadicConstruction) {
+ auto tags = TagsComponent("player", "indestructible");
+ unordered_set<string> expected;
+ expected.insert("player");
+ expected.insert("indestructible");
+ ASSERT_TRUE(expected == tags.tags);
+}
+
+TEST(TagsComponentTest, TestEntitiesWithTag) {
+ EventManager ev;
+ EntityManager en(ev);
+ Entity a = en.create();
+ en.assign<Position>(a);
+ for (int i = 0; i < 99; ++i) {
+ auto e = en.create();
+ en.assign<Position>(e);
+ en.assign<TagsComponent>(e, "positionable");
+ }
+ en.assign<TagsComponent>(a, "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
new file mode 100644
index 0000000..fefc1cd
--- /dev/null
+++ b/entityx/Entity.cc
@@ -0,0 +1,18 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include <algorithm>
+#include "entityx/Entity.h"
+
+namespace entity {
+
+BaseComponent::Family BaseComponent::family_counter_ = 0;
+
+}
diff --git a/entityx/Entity.h b/entityx/Entity.h
new file mode 100644
index 0000000..e832c15
--- /dev/null
+++ b/entityx/Entity.h
@@ -0,0 +1,419 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#pragma once
+
+
+#include <inttypes.h>
+#include <algorithm>
+#include <bitset>
+#include <cassert>
+#include <iterator>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+#include <boost/unordered_set.hpp>
+#include <boost/shared_ptr.hpp>
+#include <glog/logging.h>
+#include "entityx/Event.h"
+
+namespace entity {
+
+/**
+ * Entity handle.
+ */
+typedef uint64_t Entity;
+
+
+class EntityManager;
+
+
+/**
+ * Base component class, only used for insertion into collections.
+ *
+ * Family is used for registration.
+ */
+struct BaseComponent {
+ public:
+ typedef uint64_t Family;
+
+ protected:
+ static Family family_counter_;
+};
+
+
+/**
+ * Component implementations should inherit from this.
+ *
+ * Components MUST provide a no-argument constructor.
+ * Components SHOULD provide convenience constructors for initializing on assignment to an Entity.
+ *
+ * This is a struct to imply that components should be data-only.
+ *
+ * Usage:
+ *
+ * struct Position : public Component<Position> {
+ * Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+ *
+ * float x, y;
+ * };
+ *
+ * family() is used for registration.
+ */
+template <typename Derived>
+struct Component : public BaseComponent {
+ public:
+ /**
+ * Emitted when a component of this type is added to an entity.
+ */
+ class AddEvent : public Event<AddEvent> {
+ public:
+ AddEvent(Entity entity, boost::shared_ptr<Derived> component) :
+ entity(entity), component(component) {}
+
+ Entity entity;
+ boost::shared_ptr<Derived> component;
+ };
+
+
+ /// Used internally for registration.
+ static Family family() {
+ static Family family = family_counter_++;
+ // The 64-bit bitmask supports a maximum of 64 components.
+ assert(family < 64);
+ return family;
+ }
+};
+
+
+/**
+ * Emitted when an entity is added to the system.
+ */
+struct EntityCreatedEvent : public Event<EntityCreatedEvent> {
+ EntityCreatedEvent(EntityManager &manager, Entity entity) :
+ manager(manager), entity(entity) {}
+
+ EntityManager &manager;
+ Entity entity;
+};
+
+
+struct EntityDestroyedEvent : public Event<EntityDestroyedEvent> {
+ EntityDestroyedEvent(EntityManager &manager, Entity entity) :
+ manager(manager), entity(entity) {}
+
+ EntityManager &manager;
+ Entity entity;
+};
+
+
+/**
+ * Emitted when any component is added to an entity.
+ */
+struct ComponentAddedEvent : public Event<ComponentAddedEvent> {
+ ComponentAddedEvent(EntityManager &manager, Entity entity, boost::shared_ptr<BaseComponent> component) :
+ manager(manager), entity(entity), component(component) {}
+
+ EntityManager &manager;
+ Entity entity;
+ boost::shared_ptr<BaseComponent> component;
+};
+
+
+/**
+ * Manages Entity creation and component assignment.
+ *
+ * eg.
+ * EntityManager e;
+ *
+ * Entity player = e.create();
+ *
+ * e.assign<Movable>(player);
+ * e.assign<Physical>(player);
+ * e.assign<Scriptable>(player);
+ * Controllable *controllable = e.assign<Controllable>(player);
+ */
+class EntityManager : boost::noncopyable {
+ private:
+ typedef std::vector<boost::shared_ptr<BaseComponent>> EntitiesComponent;
+
+ public:
+ EntityManager(EventManager &event_manager) : event_manager_(event_manager) {}
+
+ class View {
+ private:
+ public:
+ typedef boost::function<bool (EntityManager &, Entity)> Predicate;
+
+ /// A predicate that excludes entities that don't match the given component mask.
+ class ComponentMaskPredicate {
+ public:
+ ComponentMaskPredicate(const std::vector<uint64_t> &entity_bits, uint64_t mask) : entity_bits_(entity_bits), mask_(mask) {}
+
+ bool operator () (EntityManager &, Entity entity) {
+ return (entity_bits_.at(entity) & mask_) == mask_;
+ }
+
+ private:
+ const std::vector<uint64_t> &entity_bits_;
+ uint64_t mask_;
+ };
+
+ /// An iterator over a view of the entities in an EntityManager.
+ class Iterator : public std::iterator<std::input_iterator_tag, Entity> {
+ public:
+ Iterator &operator ++ () {
+ ++i_;
+ next();
+ return *this;
+ }
+ bool operator == (const Iterator& rhs) const { return i_ == rhs.i_; }
+ bool operator != (const Iterator& rhs) const { return i_ != rhs.i_; }
+ Entity & operator * () { return i_; }
+ const Entity & operator * () const { return i_; }
+
+ private:
+ friend class View;
+
+ Iterator(EntityManager &manager, const std::vector<Predicate> &predicates, Entity entity) : manager_(manager), predicates_(predicates), i_(entity) {
+ next();
+ }
+
+ void next() {
+ while (i_ < manager_.size() && !predicate()) {
+ ++i_;
+ }
+ }
+
+ bool predicate() {
+ for (auto &p : predicates_) {
+ if (!p(manager_, i_)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ EntityManager &manager_;
+ const std::vector<Predicate> predicates_;
+ Entity i_;
+ };
+
+ // Create a sub-view with an additional predicate.
+ View(const View &view, Predicate predicate) : manager_(view.manager_), predicates_(view.predicates_) {
+ predicates_.push_back(predicate);
+ }
+
+ Iterator begin() { return Iterator(manager_, predicates_, 0); }
+ Iterator end() { return Iterator(manager_, predicates_, manager_.size()); }
+ const Iterator begin() const { return Iterator(manager_, predicates_, 0); }
+ const Iterator end() const { return Iterator(manager_, predicates_, manager_.size()); }
+
+ private:
+ friend class EntityManager;
+
+ View(EntityManager &manager, Predicate predicate) : manager_(manager) {
+ predicates_.push_back(predicate);
+ }
+
+ EntityManager &manager_;
+ std::vector<Predicate> predicates_;
+ };
+
+ /**
+ * Number of managed entities.
+ */
+ size_t size() const { return entity_component_mask_.size(); }
+
+ /**
+ * Create a new Entity.
+ *
+ * Emits EntityCreatedEvent.
+ */
+ Entity create() {
+ Entity id;
+ if (free_list_.empty()) {
+ id = id_counter_++;
+ accomodate_entity(id);
+ } else {
+ id = *free_list_.erase(free_list_.begin());
+ }
+ event_manager_.emit<EntityCreatedEvent>(*this, id);
+ return id;
+ }
+
+ /**
+ * Destroy an existing Entity and its associated Components.
+ *
+ * Emits EntityDestroyedEvent.
+ */
+ void destroy(Entity entity) {
+ CHECK(entity < entity_component_mask_.size()) << "Entity ID outside entity vector range";
+ event_manager_.emit<EntityDestroyedEvent>(*this, entity);
+ for (auto &components : entity_components_) {
+ components.at(entity).reset();
+ }
+ entity_component_mask_.at(entity) = 0;
+ free_list_.insert(entity);
+ }
+
+ /**
+ * Check if an Entity is registered.
+ */
+ bool exists(Entity entity) {
+ if (entity_component_mask_.empty() || entity >= id_counter_) {
+ return false;
+ }
+ return free_list_.find(entity) == free_list_.end();
+ }
+
+ /**
+ * Assigns a previously constructed Component to an Entity.
+ *
+ * @returns component
+ */
+ template <typename C>
+ boost::shared_ptr<C> assign(Entity entity, boost::shared_ptr<C> component) {
+ boost::shared_ptr<BaseComponent> base = boost::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();
+
+ // TODO(alec): Figure out why this doesn't compile...gets an odd error about AddEvent not being a value.
+ //event_manager_.emit<C::AddEvent>(entity, component);
+ event_manager_.emit<ComponentAddedEvent>(*this, entity, base);
+ return component;
+ }
+
+ /**
+ * Assign a Component to an Entity, optionally passing through Component constructor arguments.
+ *
+ * Position *position = em.assign<Position>(e, x, y);
+ *
+ * @returns Newly created component.
+ */
+ template <typename C, typename ... Args>
+ boost::shared_ptr<C> assign(Entity entity, Args && ... args) {
+ return assign<C>(entity, boost::make_shared<C>(args ...));
+ }
+
+ /**
+ * Retrieve a Component assigned to an Entity.
+ *
+ * @returns Component instance, or empty shared_ptr<> if the Entity does not have that Component.
+ */
+ template <typename C>
+ boost::shared_ptr<C> component(Entity id) {
+ // We don't bother checking the component mask, as we return a nullptr anyway.
+ if (C::family() >= entity_components_.size()) {
+ return boost::shared_ptr<C>();
+ }
+ auto &c = entity_components_.at(C::family()).at(id);
+ return boost::static_pointer_cast<C>(c);
+ }
+
+ /**
+ * Get all entities with the given component.
+ */
+ template <typename C>
+ View entities_with_components() {
+ auto mask = component_mask<C>();
+ return View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask));
+ }
+
+ /**
+ * Find Entities that have all of the specified Components.
+ */
+ template <typename C1, typename C2, typename ... Components>
+ View entities_with_components() {
+ auto mask = component_mask<C1, C2, Components ...>();
+ return View(*this, View::ComponentMaskPredicate(entity_component_mask_, mask));
+ }
+
+ /**
+ * Unpack components directly into pointers.
+ *
+ * Components missing from the entity will be set to nullptr.
+ *
+ * Useful for fast bulk iterations.
+ *
+ * Position *p;
+ * Direction *d;
+ * unpack<Position, Direction>(e, p, d);
+ *
+ * Ideally this process would be more like:
+ *
+ * for (auto components : em.unpack_entities<Position, Direction>()) {
+ * }
+ */
+ template <typename A>
+ void unpack(Entity id, A *&a) {
+ a = component<A>(id).get();
+ }
+
+ /**
+ * Unpack components directly into pointers.
+ *
+ * Components missing from the entity will be set to nullptr.
+ *
+ * Useful for fast bulk iterations.
+ *
+ * Position *p;
+ * Direction *d;
+ * unpack<Position, Direction>(e, p, d);
+ */
+ template <typename A, typename B, typename ... Args>
+ void unpack(Entity id, A *&a, B *&b, Args *& ... args) {
+ a = component<A>(id).get();
+ unpack<B, Args ...>(id, b, args ...);
+ }
+
+ private:
+ template <typename C>
+ uint64_t component_mask() {
+ return uint64_t(1) << C::family();
+ }
+
+ template <typename C1, typename C2, typename ... Components>
+ uint64_t component_mask() {
+ return component_mask<C1>() | component_mask<C2, Components ...>();
+ }
+
+ inline void accomodate_entity(Entity entity) {
+ if (entity_component_mask_.size() <= entity) {
+ entity_component_mask_.resize(entity + 1);
+ for (auto &components : entity_components_) {
+ components.resize(entity + 1);
+ }
+ }
+ }
+
+ inline void accomodate_component(BaseComponent::Family family) {
+ if (entity_components_.size() <= family) {
+ entity_components_.resize(family + 1);
+ for (auto &components : entity_components_) {
+ components.resize(id_counter_);
+ }
+ }
+ }
+
+ Entity id_counter_ = 0;
+
+ EventManager &event_manager_;
+ // A nested array of: components = entity_components_[family][entity]
+ std::vector<EntitiesComponent> entity_components_;
+ // Bitmask of components associated with each entity. Index into the vector is the Entity.
+ std::vector<uint64_t> entity_component_mask_;
+ // List of available Entity IDs.
+ boost::unordered_set<Entity> free_list_;
+};
+
+}
diff --git a/entityx/Entity_test.cc b/entityx/Entity_test.cc
new file mode 100644
index 0000000..8feb91a
--- /dev/null
+++ b/entityx/Entity_test.cc
@@ -0,0 +1,207 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include <iterator>
+#include <string>
+#include <vector>
+#include <boost/ref.hpp>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+#include "entityx/Entity.h"
+
+using namespace std;
+using namespace boost;
+using namespace entity;
+
+
+template <typename T>
+int size(const T &t) {
+ int n = 0;
+ for (auto i : t) {
+ ++n;
+ }
+ return n;
+}
+
+struct Position : Component<Position> {
+ Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+
+ float x, y;
+};
+
+struct Direction : Component<Direction> {
+ Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+
+ float x, y;
+};
+
+
+class EntityManagerTest : public ::testing::Test {
+ protected:
+ EntityManagerTest() : em(ev) {}
+
+ EventManager ev;
+ EntityManager em;
+
+ virtual void SetUp() {
+ }
+};
+
+
+TEST_F(EntityManagerTest, TestCreateEntity) {
+ Entity e = em.create();
+ ASSERT_TRUE(em.exists(e));
+}
+
+TEST_F(EntityManagerTest, TestComponentConstruction) {
+ auto e = em.create();
+ auto p = em.assign<Position>(e, 1, 2);
+ auto cp = em.component<Position>(e);
+ ASSERT_EQ(p, cp);
+ ASSERT_FLOAT_EQ(1.0, cp->x);
+ ASSERT_FLOAT_EQ(2.0, cp->y);
+}
+
+TEST_F(EntityManagerTest, TestComponentCreationWithObject) {
+ auto e = em.create();
+ auto p = em.assign(e, make_shared<Position>(1.0, 2.0));
+ auto cp = em.component<Position>(e);
+ ASSERT_EQ(p, cp);
+ ASSERT_FLOAT_EQ(1.0, cp->x);
+ ASSERT_FLOAT_EQ(2.0, cp->y);
+}
+
+TEST_F(EntityManagerTest, TestDestroyEntity) {
+ Entity e = em.create();
+ Entity f = em.create();
+ auto ep = em.assign<Position>(e);
+ em.assign<Position>(f);
+ em.assign<Direction>(e);
+ em.assign<Direction>(f);
+
+ ASSERT_EQ(2, ep.use_count());
+ ASSERT_TRUE(em.exists(e));
+ ASSERT_TRUE(em.exists(f));
+ ASSERT_TRUE(em.component<Position>(e));
+ ASSERT_TRUE(em.component<Direction>(e));
+ ASSERT_TRUE(em.component<Position>(f));
+ ASSERT_TRUE(em.component<Direction>(f));
+
+ em.destroy(e);
+
+ ASSERT_FALSE(em.exists(e));
+ ASSERT_TRUE(em.exists(f));
+ ASSERT_FALSE(em.component<Position>(e));
+ ASSERT_FALSE(em.component<Direction>(e));
+ ASSERT_TRUE(em.component<Position>(f));
+ ASSERT_TRUE(em.component<Direction>(f));
+ ASSERT_EQ(1, ep.use_count());
+}
+
+TEST_F(EntityManagerTest, TestGetEntitiesWithComponent) {
+ Entity e = em.create();
+ Entity f = em.create();
+ Entity g = em.create();
+ em.assign<Position>(e);
+ em.assign<Direction>(e);
+ em.assign<Position>(f);
+ em.assign<Position>(g);
+ ASSERT_EQ(3, size(em.entities_with_components<Position>()));
+ ASSERT_EQ(1, size(em.entities_with_components<Direction>()));
+}
+
+TEST_F(EntityManagerTest, TestGetEntitiesWithIntersectionOfComponents) {
+ vector<Entity> entities;
+ for (int i = 0; i < 150; ++i) {
+ Entity e = em.create();
+ entities.push_back(e);
+ if (i % 2 == 0)
+ em.assign<Position>(e);
+ if (i % 3 == 0)
+ em.assign<Direction>(e);
+
+ }
+ ASSERT_EQ(50, size(em.entities_with_components<Direction>()));
+ ASSERT_EQ(75, size(em.entities_with_components<Position>()));
+ ASSERT_EQ(25, size(em.entities_with_components<Direction, Position>()));
+}
+
+TEST_F(EntityManagerTest, TestUnpack) {
+ Entity e = em.create();
+ auto p = em.assign<Position>(e);
+ auto d = em.assign<Direction>(e);
+
+ Position *up;
+ Direction *ud;
+ em.unpack<Position, Direction>(e, up, ud);
+ ASSERT_EQ(p.get(), up);
+ ASSERT_EQ(d.get(), ud);
+}
+
+TEST_F(EntityManagerTest, TestUnpackNullMissing) {
+ Entity e = em.create();
+ auto p = em.assign<Position>(e);
+
+ Position *up = reinterpret_cast<Position*>(0Xdeadbeef);
+ Direction *ud = reinterpret_cast<Direction*>(0Xdeadbeef);
+ ASSERT_EQ(reinterpret_cast<Direction*>(0xdeadbeef), ud);
+ em.unpack<Position, Direction>(e, up, ud);
+ ASSERT_EQ(p.get(), up);
+ ASSERT_EQ(nullptr, ud);
+}
+
+TEST_F(EntityManagerTest, TestComponentIdsDiffer) {
+ ASSERT_NE(Position::family(), Direction::family());
+}
+
+
+TEST_F(EntityManagerTest, TestEntityCreatedEvent) {
+ struct EntityCreatedEventReceiver : public Receiver<EntityCreatedEventReceiver> {
+ void receive(const EntityCreatedEvent &event) {
+ created.push_back(event.entity);
+ }
+
+ vector<Entity> created;
+ };
+
+ EntityCreatedEventReceiver receiver;
+ ev.subscribe<EntityCreatedEvent>(receiver);
+
+ ASSERT_EQ(0, receiver.created.size());
+ for (int i = 0; i < 10; ++i) {
+ em.create();
+ }
+ ASSERT_EQ(10, receiver.created.size());
+};
+
+
+TEST_F(EntityManagerTest, TestEntityDestroyedEvent) {
+ struct EntityDestroyedEventReceiver : public Receiver<EntityDestroyedEventReceiver> {
+ void receive(const EntityDestroyedEvent &event) {
+ destroyed.push_back(event.entity);
+ }
+
+ vector<Entity> destroyed;
+ };
+
+ EntityDestroyedEventReceiver receiver;
+ ev.subscribe<EntityDestroyedEvent>(receiver);
+
+ ASSERT_EQ(0, receiver.destroyed.size());
+ 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);
+ }
+ ASSERT_TRUE(entities == receiver.destroyed);
+};
diff --git a/entityx/Event.cc b/entityx/Event.cc
new file mode 100644
index 0000000..484a17c
--- /dev/null
+++ b/entityx/Event.cc
@@ -0,0 +1,17 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include "entityx/Event.h"
+
+namespace entity {
+
+BaseEvent::Family BaseEvent::family_counter_ = 0;
+
+}
diff --git a/entityx/Event.h b/entityx/Event.h
new file mode 100644
index 0000000..510da8f
--- /dev/null
+++ b/entityx/Event.h
@@ -0,0 +1,153 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/signal.hpp>
+#include <boost/unordered_map.hpp>
+
+
+namespace entity {
+
+
+/// Used internally by the EventManager.
+class BaseEvent {
+ public:
+ typedef boost::shared_ptr<BaseEvent> Ptr;
+ typedef uint64_t Family;
+
+ virtual ~BaseEvent() {}
+
+ protected:
+ static Family family_counter_;
+};
+
+
+/**
+ * Event types should subclass from this.
+ *
+ * struct Explosion : public Event<Explosion> {
+ * Explosion(int damage) : damage(damage) {}
+ * int damage;
+ * };
+ */
+template <typename Derived>
+class Event : public BaseEvent {
+ public:
+ typedef boost::shared_ptr<Event<Derived>> Ptr;
+
+ /// Used internally for registration.
+ static Family family() {
+ static Family family = family_counter_++;
+ return family;
+ }
+};
+
+
+class BaseReceiver {
+ public:
+ virtual ~BaseReceiver() {
+ for (auto connection : connections_) {
+ connection.disconnect();
+ }
+ }
+
+ private:
+ friend class EventManager;
+ std::list<boost::signals::connection> connections_;
+};
+
+template <typename Derived>
+class Receiver : public BaseReceiver {
+ public:
+ virtual ~Receiver() {}
+};
+
+
+/**
+ * Handles event subscription and delivery.
+ *
+ * Subscriptions are automatically removed when receivers are destroyed..
+ */
+class EventManager : boost::noncopyable {
+ public:
+
+ /**
+ * Subscribe an object to receive events of type E.
+ *
+ * Receivers must be subclasses of Receiver and must implement a receive() method accepting the given event type.
+ *
+ * eg.
+ *
+ * struct ExplosionReceiver : public Receiver<ExplosionReceiver> {
+ * void receive(const Explosion &explosion) {
+ * }
+ * };
+ *
+ * ExplosionReceiver receiver;
+ * em.subscribe<Explosion>(receiver);
+ */
+ template <typename E, typename Receiver>
+ void subscribe(Receiver &receiver) {
+ void (Receiver::*receive) (const E &) = &Receiver::receive;
+ auto sig = signal_for(E::family());
+ auto wrapper = EventCallbackWrapper<E>(boost::bind(receive, &receiver, _1));
+ static_cast<BaseReceiver&>(receiver).connections_.push_back(sig->connect(wrapper));
+ }
+
+ /**
+ * Emit an event to receivers.
+ *
+ * This method constructs a new event object of type E with the provided arguments, then delivers it to all receivers.
+ *
+ * eg.
+ *
+ * EntityManager em;
+ * em.emit<Explosion>(10);
+ *
+ */
+ template <typename E, typename ... Args>
+ void emit(Args && ... args) {
+ E event(args ...);
+ auto sig = signal_for(E::family());
+ (*sig)(static_cast<BaseEvent*>(&event));
+ }
+
+ private:
+ typedef boost::signal<void (const BaseEvent*)> EventSignal;
+ typedef boost::shared_ptr<EventSignal> EventSignalPtr;
+
+ EventSignalPtr signal_for(int id) {
+ auto it = handlers_.find(id);
+ if (it == handlers_.end()) {
+ EventSignalPtr sig(boost::make_shared<EventSignal>());
+ handlers_.insert(make_pair(id, sig));
+ return sig;
+ }
+ return it->second;
+ }
+
+ // Functor used as a event signal callback that casts to E.
+ template <typename E>
+ struct EventCallbackWrapper {
+ EventCallbackWrapper(boost::function<void (const E &)> callback) : callback(callback) {}
+ void operator () (const BaseEvent* event) { callback(*(static_cast<const E*>(event))); }
+ boost::function<void (const E &)> callback;
+ };
+
+ boost::unordered_map<int, EventSignalPtr> handlers_;
+};
+
+}
diff --git a/entityx/Event_test.cc b/entityx/Event_test.cc
new file mode 100644
index 0000000..63b0529
--- /dev/null
+++ b/entityx/Event_test.cc
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include <string>
+#include <vector>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+#include "entityx/Event.h"
+
+using namespace entity;
+using namespace boost;
+
+struct Explosion : public Event<Explosion> {
+ Explosion(int damage) : damage(damage) {}
+ int damage;
+};
+
+struct ExplosionSystem : public Receiver<ExplosionSystem> {
+ void receive(const Explosion &explosion) {
+ damage_received += explosion.damage;
+ }
+
+ int damage_received = 0;
+};
+
+TEST(EventManagerTest, TestEmitReceive) {
+ EventManager em;
+ ExplosionSystem explosion_system;
+ em.subscribe<Explosion>(explosion_system);
+ ASSERT_EQ(0, explosion_system.damage_received);
+ em.emit<Explosion>(10);
+ ASSERT_EQ(10, explosion_system.damage_received);
+}
diff --git a/entityx/Makefile b/entityx/Makefile
new file mode 100644
index 0000000..039fce0
--- /dev/null
+++ b/entityx/Makefile
@@ -0,0 +1,66 @@
+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+=-L. -lentity
+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/System.cc b/entityx/System.cc
new file mode 100644
index 0000000..4c05f2b
--- /dev/null
+++ b/entityx/System.cc
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include "entityx/System.h"
+
+namespace entity {
+
+BaseSystem::Family BaseSystem::family_counter_;
+
+void SystemManager::configure() {
+ for (auto pair : systems_) {
+ pair.second->configure(events_);
+ }
+ initialized_ = true;
+}
+
+}
diff --git a/entityx/System.h b/entityx/System.h
new file mode 100644
index 0000000..ad6a9ba
--- /dev/null
+++ b/entityx/System.h
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#pragma once
+
+
+#include <inttypes.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/unordered_map.hpp>
+#include <glog/logging.h>
+#include "entityx/Entity.h"
+#include "entityx/Event.h"
+
+
+namespace entity {
+
+/**
+ * Base System class. Generally should not be directly used, instead see System<Derived>.
+ */
+class BaseSystem : boost::noncopyable {
+ public:
+ typedef uint64_t Family;
+
+ virtual ~BaseSystem() {}
+
+ /**
+ * Called once all Systems have been added to the SystemManager.
+ *
+ * Typically used to set up event handlers.
+ */
+ virtual void configure(EventManager &events) {}
+
+ /**
+ * Apply System behavior.
+ *
+ * Called every game step.
+ */
+ virtual void update(EntityManager &entities, EventManager &events, double dt) = 0;
+
+ static Family family_counter_;
+
+ protected:
+ boost::shared_ptr<EntityManager> entities;
+};
+
+
+/**
+ * Use this class when implementing Systems.
+ *
+ * struct MovementSystem : public System<MovementSystem> {
+ * void update(EntityManager &entities, EventManager &events, double dt) {
+ * // Do stuff to/with entities...
+ * }
+ * }
+ */
+template <typename Derived>
+class System : public BaseSystem {
+ public:
+ virtual ~System() {}
+
+ static Family family() {
+ static Family family = family_counter_++;
+ return family;
+ }
+};
+
+
+class SystemManager : boost::noncopyable {
+ public:
+ SystemManager(EntityManager &entities, EventManager &events) : entities_(entities), events_(events) {}
+
+ /**
+ * Add a System to the SystemManager.
+ *
+ * Must be called before Systems can be used.
+ *
+ * eg.
+ * auto movement = system.add<MovementSystem>()
+ */
+ template <typename S, typename ... Args>
+ boost::shared_ptr<S> add(Args && ... args) {
+ boost::shared_ptr<S> s(new S(args ...));
+ systems_.insert(std::make_pair(S::family(), s));
+ return s;
+ }
+
+ template <typename S>
+ boost::shared_ptr<S> system() {
+ auto it = systems_.find(S::family());
+ CHECK(it != systems_.end());
+ return it == systems_.end()
+ ? boost::shared_ptr<S>()
+ : boost::static_pointer_cast<S>(it->second);
+ }
+
+ /**
+ * Call the System::update() method for a registered system.
+ */
+ template <typename S>
+ void update(double dt) {
+ CHECK(initialized_) << "SystemManager::configure() not called";
+ boost::shared_ptr<S> s = system<S>();
+ s->update(entities_, events_, dt);
+ }
+
+ /**
+ * Configure the system. Call after adding all Systems.
+ *
+ * This is typically used to set up event handlers.
+ */
+ void configure();
+
+ private:
+ bool initialized_ = false;
+ EntityManager &entities_;
+ EventManager &events_;
+ boost::unordered_map<BaseSystem::Family, boost::shared_ptr<BaseSystem>> systems_;
+};
+
+}
diff --git a/entityx/System_test.cc b/entityx/System_test.cc
new file mode 100644
index 0000000..6133eb0
--- /dev/null
+++ b/entityx/System_test.cc
@@ -0,0 +1,117 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include <string>
+#include <vector>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+#include "entityx/World.h"
+#include "entityx/System.h"
+
+
+using namespace std;
+using namespace boost;
+using namespace entity;
+
+struct Position : Component<Position> {
+ Position(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+
+ float x, y;
+};
+
+struct Direction : Component<Direction> {
+ Direction(float x = 0.0f, float y = 0.0f) : x(x), y(y) {}
+
+ float x, y;
+};
+
+
+class MovementSystem : public System<MovementSystem> {
+ public:
+ MovementSystem(string label = "") : label(label) {}
+
+ void update(EntityManager &es, EventManager &events, double) override {
+ auto entities = es.entities_with_components<Position, Direction>();
+ Position *position;
+ Direction *direction;
+ for (auto entity : entities) {
+ es.unpack<Position, Direction>(entity, position, direction);
+ position->x += direction->x;
+ position->y += direction->y;
+ }
+ }
+
+ string label;
+};
+
+
+class TestWorld : public entity::World {
+ public:
+ std::vector<Entity> entities;
+
+ SystemManager &sm() { return system_manager; }
+ EntityManager &em() { return entity_manager; }
+
+ protected:
+ void configure() override {
+ }
+
+ void initialize() override {
+ for (int i = 0; i < 150; ++i) {
+ Entity e = entity_manager.create();
+ entities.push_back(e);
+ if (i % 2 == 0)
+ entity_manager.assign<Position>(e, 1, 2);
+ if (i % 3 == 0)
+ entity_manager.assign<Direction>(e, 1, 1);
+ }
+ }
+
+ void update(double dt) override {
+ }
+};
+
+
+class SystemManagerTest : public ::testing::Test {
+ protected:
+ TestWorld world;
+
+ virtual void SetUp() override {
+ world.start();
+ }
+};
+
+
+TEST_F(SystemManagerTest, TestConstructSystemWithArgs) {
+ world.sm().add<MovementSystem>("movement");
+ world.sm().configure();
+
+ ASSERT_EQ("movement", world.sm().system<MovementSystem>()->label);
+}
+
+
+TEST_F(SystemManagerTest, TestApplySystem) {
+ world.sm().add<MovementSystem>();
+ world.sm().configure();
+
+ world.sm().update<MovementSystem>(0.0);
+ Position *position;
+ Direction *direction;
+ for (auto entity : world.entities) {
+ world.em().unpack<Position, Direction>(entity, position, direction);
+ if (position && direction) {
+ ASSERT_FLOAT_EQ(2.0, position->x);
+ ASSERT_FLOAT_EQ(3.0, position->y);
+ } else if (position) {
+ ASSERT_FLOAT_EQ(1.0, position->x);
+ ASSERT_FLOAT_EQ(2.0, position->y);
+ }
+ }
+}
diff --git a/entityx/World.cc b/entityx/World.cc
new file mode 100644
index 0000000..5e8d8db
--- /dev/null
+++ b/entityx/World.cc
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#include "World.h"
+
+namespace entity {
+
+void World::start() {
+ configure();
+ system_manager.configure();
+ initialize();
+}
+
+void World::run() {
+ running_ = true;
+ double dt;
+ while (running_) {
+ dt = timer_.elapsed();
+ timer_.restart();
+ update(dt);
+ }
+}
+
+void World::stop() {
+ running_ = false;
+}
+
+}
diff --git a/entityx/World.h b/entityx/World.h
new file mode 100644
index 0000000..0cca1e2
--- /dev/null
+++ b/entityx/World.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2012 Alec Thomas <alec@swapoff.org>
+ * All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.
+ *
+ * Author: Alec Thomas <alec@swapoff.org>
+ */
+
+#pragma once
+
+#include <boost/timer.hpp>
+#include "entityx/Entity.h"
+#include "entityx/Event.h"
+#include "entityx/System.h"
+
+namespace entity {
+
+class World {
+ public:
+ World() : entity_manager(event_manager), system_manager(entity_manager, event_manager) {}
+ virtual ~World() {}
+
+ void start();
+ void run();
+ void stop();
+
+ protected:
+ /**
+ * Configure the world.
+ *
+ * This is called once on World initialization. It is typically used to add Systems to the world, load permanent
+ * resources, global configuration, etc.
+ */
+ virtual void configure() = 0;
+
+ /**
+ * Initialize the entities and events in the world.
+ *
+ * Typically used when
+ */
+ virtual void initialize() = 0;
+
+ /**
+ * Update the world.
+ */
+ virtual void update(double dt) = 0;
+
+ EventManager event_manager;
+ EntityManager entity_manager;
+ SystemManager system_manager;
+
+ private:
+ boost::timer timer_;
+ bool running_ = false;
+};
+
+}