]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Initial import.
authorAlec Thomas <alec@swapoff.org>
Fri, 21 Sep 2012 01:24:08 +0000 (21:24 -0400)
committerAlec Thomas <alec@swapoff.org>
Fri, 21 Sep 2012 01:24:08 +0000 (21:24 -0400)
18 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
Makefile.conf [new file with mode: 0644]
entityx/Components.cc [new file with mode: 0644]
entityx/Components.h [new file with mode: 0644]
entityx/Components_test.cc [new file with mode: 0644]
entityx/Entity.cc [new file with mode: 0644]
entityx/Entity.h [new file with mode: 0644]
entityx/Entity_test.cc [new file with mode: 0644]
entityx/Event.cc [new file with mode: 0644]
entityx/Event.h [new file with mode: 0644]
entityx/Event_test.cc [new file with mode: 0644]
entityx/Makefile [new file with mode: 0644]
entityx/System.cc [new file with mode: 0644]
entityx/System.h [new file with mode: 0644]
entityx/System_test.cc [new file with mode: 0644]
entityx/World.cc [new file with mode: 0644]
entityx/World.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..dc667ff
--- /dev/null
@@ -0,0 +1,4 @@
+.*.dep
+*.a
+*.so
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..49ba7e3
--- /dev/null
@@ -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 (file)
index 0000000..0103144
--- /dev/null
@@ -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 (file)
index 0000000..8c96fa7
--- /dev/null
@@ -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 (file)
index 0000000..e32fb46
--- /dev/null
@@ -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 (file)
index 0000000..fefc1cd
--- /dev/null
@@ -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 (file)
index 0000000..e832c15
--- /dev/null
@@ -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 (file)
index 0000000..8feb91a
--- /dev/null
@@ -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 (file)
index 0000000..484a17c
--- /dev/null
@@ -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 (file)
index 0000000..510da8f
--- /dev/null
@@ -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 (file)
index 0000000..63b0529
--- /dev/null
@@ -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 (file)
index 0000000..039fce0
--- /dev/null
@@ -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 (file)
index 0000000..4c05f2b
--- /dev/null
@@ -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 (file)
index 0000000..ad6a9ba
--- /dev/null
@@ -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 (file)
index 0000000..6133eb0
--- /dev/null
@@ -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 (file)
index 0000000..5e8d8db
--- /dev/null
@@ -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 (file)
index 0000000..0cca1e2
--- /dev/null
@@ -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;
+};
+
+}