]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Event delivery to/from Python and C++.
authorAlec Thomas <alec@swapoff.org>
Fri, 20 Dec 2013 05:24:01 +0000 (00:24 -0500)
committerAlec Thomas <alec@swapoff.org>
Fri, 20 Dec 2013 05:24:01 +0000 (00:24 -0500)
12 files changed:
.gitignore
CMakeLists.txt
entityx/Entity.cc
entityx/Entity.h
entityx/python/PythonSystem.cc
entityx/python/PythonSystem.h
entityx/python/PythonSystem_test.cc
entityx/python/README.md
entityx/python/entityx/__init__.py
entityx/python/entityx/tests/create_entities_from_python_test.py
entityx/python/entityx/tests/event_emit_test.py
entityx/python/entityx/tests/event_test.py

index b94fd740561f87e7c7d85c5b3f1f228d5f0e49c4..5c6d0fdab3790c28db3cd49e9d7a85c0b12077cc 100644 (file)
@@ -6,3 +6,5 @@ build/*
 entityx/config.h
 Makefile
 html/
+Vagrantfile
+.vagrant
index 104efb10c85ded4f3afa33f28b56b0a58bfe761c..f49c75f3811635ddd1c98b6185250baf7c268ffa 100644 (file)
@@ -146,7 +146,6 @@ if (ENTITYX_BUILD_PYTHON AND Boost_PYTHON_LIBRARY)
         list(APPEND install_libs entityx_python_shared)
     endif (ENTITYX_BUILD_SHARED)
     set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS})
-    include(CheckNeedGetPointer.cmake)
 endif (ENTITYX_BUILD_PYTHON AND Boost_PYTHON_LIBRARY)
 
 if (ENTITYX_BUILD_TESTING)
index dfc6bb5f6a45442ef1c881f2977a183189157ca5..48b582772ecc1633c201f7a4e35e96caf3cbbc98 100644 (file)
@@ -27,8 +27,8 @@ void Entity::destroy() {
   invalidate();
 }
 
-bool Entity::valid() const {
-  return !manager_.expired() && manager_.lock()->valid(id_);
+std::bitset<entityx::MAX_COMPONENTS> Entity::component_mask() const {
+  return manager_.lock()->component_mask(id_);
 }
 
 EntityManager::EntityManager(ptr<EventManager> event_manager) : event_manager_(event_manager) {
index ba1822a16bf6b88390419ccc0a7287fb87484ce4..957737539e2b97e1fcd06aa92325a78408e54ea8 100644 (file)
@@ -71,9 +71,15 @@ public:
   static const Id INVALID;
 
   Entity() {}
-  Entity(const Entity &) = default;
-  Entity(Entity &&) = default;
-  Entity &operator = (const Entity &) = default;
+  Entity(const ptr<EntityManager> &manager, Entity::Id id) : manager_(manager), id_(id) {
+  }
+  Entity(const Entity &other) : manager_(other.manager_), id_(other.id_) {
+  }
+  Entity &operator = (const Entity &other) {
+    manager_ = other.manager_;
+    id_ = other.id_;
+    return *this;
+  }
 
   /**
    * Check if Entity handle is invalid.
@@ -129,18 +135,16 @@ public:
    */
   void destroy();
 
- private:
-  friend class EntityManager;
-
-  Entity(ptr<EntityManager> manager, Entity::Id id) : manager_(manager), id_(id) {}
+  std::bitset<entityx::MAX_COMPONENTS> component_mask() const;
 
+ private:
   weak_ptr<EntityManager> manager_;
   Entity::Id id_ = INVALID;
 };
 
 
 inline std::ostream &operator << (std::ostream &out, const Entity::Id &id) {
-  out << "Entity::Id(" << std::hex << id.id() << ")";
+  out << "Entity::Id(" << id.index() << "." << id.version() << ")";
   return out;
 }
 
@@ -551,6 +555,10 @@ class EntityManager : entityx::help::NonCopyable, public enable_shared_from_this
    */
   void destroy_all();
 
+  ComponentMask component_mask(Entity::Id id) {
+    return entity_component_mask_.at(id.index());
+  }
+
  private:
   template <typename C>
   ComponentMask component_mask() {
@@ -643,4 +651,10 @@ void Entity::unpack(ptr<A> &a, ptr<Args> & ... args) {
   manager_.lock()->unpack(id_, a, args ...);
 }
 
+inline bool Entity::valid() const {
+  return !manager_.expired() && manager_.lock()->valid(id_);
+}
+
+
+
 }  // namespace entityx
index 220003b3d210a98d24e7761406b1588034fd2832..373948d464887c20fc3126f4b080bd484ff0f4fc 100644 (file)
@@ -9,13 +9,13 @@
  */
 
 // http://docs.python.org/2/extending/extending.html
+#include <boost/noncopyable.hpp>
 #include <Python.h>
 #include <cassert>
 #include <string>
 #include <iostream>
 #include <sstream>
 #include "entityx/python/PythonSystem.h"
-#include "entityx/help/NonCopyable.h"
 
 namespace py = boost::python;
 
@@ -26,16 +26,6 @@ namespace python {
 static const py::object None;
 
 
-/**
- * Convert Entity::Id to a Python long.
- */
-struct EntityIdToPythonInteger {
-  static PyObject* convert(Entity::Id const& id) {
-    return py::incref(py::long_(id.id()).ptr());
-  }
-};
-
-
 class PythonEntityXLogger {
 public:
   PythonEntityXLogger() {}
@@ -66,8 +56,12 @@ private:
 };
 
 
+/**
+ * Base class for Python entities.
+ */
 struct PythonEntity {
-  PythonEntity(Entity entity) : _entity(entity) {}  // NOLINT
+  explicit PythonEntity(ptr<EntityManager> entity_manager, Entity::Id id) : _entity(Entity(entity_manager, id)) {}  // NOLINT
+  virtual ~PythonEntity() {}
 
   void destroy() {
     _entity.destroy();
@@ -75,59 +69,87 @@ struct PythonEntity {
 
   operator Entity () const { return _entity; }
 
-  void update(float dt, int frame) {}
+  virtual void update(float dt, int frame) {}
+
+  Entity::Id _entity_id() const {
+    return _entity.id();
+  }
 
   Entity _entity;
 };
 
-static std::string python_entity_repr(const PythonEntity &entity) {
+static std::string PythonEntity_repr(const PythonEntity &entity) {
   std::stringstream repr;
   repr << "<Entity " << entity._entity.id().index() << "." << entity._entity.id().version() << ">";
   return repr.str();
 }
 
 
-static std::string entity_repr(Entity entity) {
+static std::string Entity_Id_repr(Entity::Id id) {
   std::stringstream repr;
-  repr << "<Entity::Id " << entity.id().index() << "." << entity.id().version() << ">";
+  repr << "<Entity::Id " << id.index() << "." << id.version() << ">";
   return repr.str();
 }
 
-static bool entity_eq(Entity left, Entity right) {
-  return left.id() == right.id();
-}
 
+// static std::string entity_repr(Entity entity) {
+//   std::stringstream repr;
+//   repr << "<Entity::Id " << entity.id().index() << "." << entity.id().version() << ">";
+//   return repr.str();
+// }
+
+// static bool entity_eq(Entity left, Entity right) {
+//   return left.id() == right.id();
+// }
+
+
+// A to-Python converter from Entity to PythonEntity.
+struct EntityToPythonEntity {
+  static PyObject *convert(Entity entity) {
+    auto python = entity.component<PythonComponent>();
+    assert(python && "Entity does not have a PythonComponent");
+    return py::incref(python->object.ptr());
+  }
+};
+
+
+Entity::Id EntityManager_configure(ptr<EntityManager> entity_manager, py::object self) {
+  Entity entity = entity_manager->create();
+  entity.assign<PythonComponent>(self);
+  return entity.id();
+}
 
 BOOST_PYTHON_MODULE(_entityx) {
-  py::to_python_converter<Entity::Id, EntityIdToPythonInteger>();
+  py::to_python_converter<Entity, EntityToPythonEntity>();
 
   py::class_<PythonEntityXLogger>("Logger", py::no_init)
     .def("write", &PythonEntityXLogger::write);
 
-  py::class_<BaseEvent, ptr<BaseEvent>, entityx::help::NonCopyable>("BaseEvent", py::no_init);
+  py::class_<BaseEvent, ptr<BaseEvent>, boost::noncopyable>("BaseEvent", py::no_init);
 
-  py::class_<PythonEntity>("Entity", py::init<Entity>())
-    .def_readonly("_entity", &PythonEntity::_entity)
+  py::class_<PythonEntity>("Entity", py::init<ptr<EntityManager>, Entity::Id>())
+    .def_readonly("_entity_id", &PythonEntity::_entity_id)
     .def("update", &PythonEntity::update)
     .def("destroy", &PythonEntity::destroy)
-    .def("__repr__", &python_entity_repr);
+    .def("__repr__", &PythonEntity_repr);
 
-  py::class_<Entity>("RawEntity", py::no_init)
-    .add_property("id", &Entity::id)
-    .def("__eq__", &entity_eq)
-    .def("__repr__", &entity_repr);
+  py::class_<Entity::Id>("EntityId", py::no_init)
+    .def_readonly("id", &Entity::Id::id)
+    .def_readonly("index", &Entity::Id::index)
+    .def_readonly("version", &Entity::Id::version)
+    .def("__repr__", &Entity_Id_repr);
 
   py::class_<PythonComponent, ptr<PythonComponent>>("PythonComponent", py::init<py::object>())
     .def("assign_to", &assign_to<PythonComponent>)
     .def("get_component", &get_component<PythonComponent>)
     .staticmethod("get_component");
 
-  py::class_<EntityManager, ptr<EntityManager>, entityx::help::NonCopyable>("EntityManager", py::no_init)
-    .def("create", &EntityManager::create);
+  py::class_<EntityManager, ptr<EntityManager>, boost::noncopyable>("EntityManager", py::no_init)
+    .def("configure", &EntityManager_configure);
 
   void (EventManager::*emit)(const BaseEvent &) = &EventManager::emit;
 
-  py::class_<EventManager, ptr<EventManager>, entityx::help::NonCopyable>("EventManager", py::no_init)
+  py::class_<EventManager, ptr<EventManager>, boost::noncopyable>("EventManager", py::no_init)
     .def("emit", emit);
 
   py::implicitly_convertible<PythonEntity, Entity>();
@@ -253,10 +275,10 @@ void PythonSystem::receive(const ComponentAddedEvent<PythonComponent> &event) {
     py::object cls = module.attr(event.component->cls.c_str());
     py::object from_raw_entity = cls.attr("_from_raw_entity");
     if (py::len(event.component->args) == 0) {
-      event.component->object = from_raw_entity(event.entity);
+      event.component->object = from_raw_entity(event.entity.id());
     } else {
       py::list args;
-      args.append(event.entity);
+      args.append(event.entity.id());
       args.extend(event.component->args);
       event.component->object = from_raw_entity(*py::tuple(args));
     }
index 3009c6c2c8f25f737e48fd6aeeed44e69ffc57f5..d63fa383c42072a649ea766e7ed96f81255c390f 100644 (file)
@@ -124,8 +124,7 @@ private:
   }
 
   /**
-   * Delete an Entity receiver. This is called automatically by PythonSystem
-   * after testing with can_send().
+   * Delete an Entity receiver. This is called automatically by PythonSystem.
    *
    * @param entity The entity that was receiving events.
    */
@@ -144,8 +143,8 @@ private:
  * A helper function for class_ to assign a component to an entity.
  */
 template <typename Component>
-void assign_to(ptr<Component> component, Entity &entity) {  // NOLINT
-  entity.assign<Component>(component);
+void assign_to(ptr<Component> component, ptr<EntityManager> entity_manager, Entity::Id id) {
+  entity_manager->assign<Component>(id, component);
 }
 
 
@@ -154,8 +153,8 @@ void assign_to(ptr<Component> component, Entity &entity) {  // NOLINT
  * entity.
  */
 template <typename Component>
-ptr<Component> get_component(Entity &entity) {  // NOLINT
-  return entity.component<Component>();
+ptr<Component> get_component(ptr<EntityManager> entity_manager, Entity::Id id) {
+  return entity_manager->component<Component>(id);
 }
 
 
index 1d0c8fda7df4ca7fad5f3691fe7d21f1e6eb458c..d4d531cff9ec25c627c8a857a4665a9829ac9319 100644 (file)
@@ -8,18 +8,21 @@
  * Author: Alec Thomas <alec@swapoff.org>
  */
 
-// http://docs.python.org/2/extending/extending.html
+// NOTE: MUST be first include. See http://docs.python.org/2/extending/extending.html
 #include <Python.h>
+#include <gtest/gtest.h>
+#include <boost/python.hpp>
 #include <cassert>
 #include <vector>
 #include <string>
-#include <gtest/gtest.h>
-#include <boost/python.hpp>
+#include <iostream>
 #include "entityx/Entity.h"
 #include "entityx/Event.h"
 #include "entityx/python/PythonSystem.h"
 
 namespace py = boost::python;
+using std::cerr;
+using std::endl;
 using namespace entityx;
 using namespace entityx::python;
 
@@ -50,8 +53,8 @@ struct CollisionEventProxy : public PythonEventProxy, public Receiver<CollisionE
 
   void receive(const CollisionEvent &event) {
     for (auto entity : entities) {
-      auto py_entity = entity.component<PythonComponent>();
       if (entity == event.a || entity == event.b) {
+        auto py_entity = entity.component<PythonComponent>();
         py_entity->object.attr("on_collision")(event);
       }
     }
@@ -75,8 +78,8 @@ BOOST_PYTHON_MODULE(entityx_python_test) {
     .def_readwrite("y", &Direction::y);
 
   py::class_<CollisionEvent, ptr<CollisionEvent>, py::bases<BaseEvent>>("Collision", py::init<Entity, Entity>())
-    .def_readonly("a", &CollisionEvent::a)
-    .def_readonly("b", &CollisionEvent::b);
+    .add_property("a", py::make_getter(&CollisionEvent::a, py::return_value_policy<py::return_by_value>()))
+    .add_property("b", py::make_getter(&CollisionEvent::b, py::return_value_policy<py::return_by_value>()));
 }
 
 
@@ -95,7 +98,7 @@ protected:
       initentityx_python_test();
       initialized = true;
     }
-    system->add_event_proxy<CollisionEvent, CollisionEventProxy>(ev, ptr<CollisionEventProxy>(new CollisionEventProxy()));
+    system->add_event_proxy<CollisionEvent>(ev, ptr<CollisionEventProxy>(new CollisionEventProxy()));
     system->configure(ev);
   }
 
@@ -196,6 +199,7 @@ TEST_F(PythonSystemTest, TestEventDelivery) {
     Entity g = em->create();
     auto scripte = e.assign<PythonComponent>("entityx.tests.event_test", "EventTest");
     auto scriptf = f.assign<PythonComponent>("entityx.tests.event_test", "EventTest");
+    auto scriptg = g.assign<PythonComponent>("entityx.tests.event_test", "EventTest");
     ASSERT_FALSE(scripte->object.attr("collided"));
     ASSERT_FALSE(scriptf->object.attr("collided"));
     ev->emit<CollisionEvent>(f, g);
index e206a053e89f4858dda6c23952061325994c44c0..cf83ab558bf25e4e0ea58eb00a021c4b088ac804 100644 (file)
@@ -36,12 +36,15 @@ To add scripting support to your system, something like the following steps shou
 
 ### Exposing C++ Components to Python
 
-In most cases, this should be pretty simple. Given a component, provide a `boost::python` class definition, with two extra methods defined with EntityX::Python helper functions `assign_to<Component>` and `get_component<Component>`. These are used from Python to assign Python-created components to an entity and to retrieve existing components from an entity, respectively.
+In most cases, this should be pretty simple. Given a component, provide a `boost::python` class definition wrapped in `entityx::ptr<T>`, with two extra methods defined with EntityX::Python helper functions `assign_to<Component>` and `get_component<Component>`. These are used from Python to assign Python-created components to an entity and to retrieve existing components from an entity, respectively.
 
 Here's an example:
 
 ```c++
 namespace py = boost::python;
+using namespace entityx;
+using namespace entityx::python;
+
 
 struct Position : public Component<Position> {
   Position(float x = 0.0, float y = 0.0) : x(x), y(y) {}
@@ -50,10 +53,10 @@ struct Position : public Component<Position> {
 };
 
 void export_position_to_python() {
-  py::class_<PythonPosition, entityx::ptr<PythonPosition>>("Position", py::init<py::optional<float, float>>())
-    .def("assign_to", &entityx::python::assign_to<Position>)
-    .def("get_component", &entityx::python::get_component<Position>)
-    .staticmethod("get_component")
+  py::class_<PythonPosition, ptr<PythonPosition>>("Position", py::init<py::optional<float, float>>())
+    .def("assign_to", &assign_to<Position>)          // Allows this component to be assigned to an entity
+    .def("get_component", &get_component<Position>)  // Allows this component to be retrieved from an entity.
+    .staticmethod("get_component")                   // (as above)
     .def_readwrite("x", &PythonPosition::x)
     .def_readwrite("y", &PythonPosition::y);
 }
@@ -109,7 +112,7 @@ struct CollisionEventProxy : public PythonEventProxy, public Receiver<CollisionE
 };
 
 void export_collision_event_to_python() {
-  py::class_<CollisionEvent>("Collision", py::init<Entity, Entity>())
+  py::class_<CollisionEvent, ptr<CollisionEvent>, py::bases<BaseEvent>>("Collision", py::init<Entity, Entity>())
     .def_readonly("a", &CollisionEvent::a)
     .def_readonly("b", &CollisionEvent::b);
 }
@@ -121,6 +124,23 @@ BOOST_PYTHON_MODULE(mygame) {
 }
 ```
 
+
+### Sending events from Python
+
+This is relatively straight forward. Once you have exported a C++ event to Python:
+
+```python
+from entityx import Entity, emit
+from mygame import Collision
+
+
+class AnEntity(Entity): pass
+
+
+emit(Collision(AnEntity(), AnEntity()))
+```
+
+
 ### Initialization
 
 Finally, initialize the `mygame` module once, before using `PythonSystem`, with something like this:
@@ -138,8 +158,8 @@ Then create and destroy `PythonSystem` as necessary:
 vector<string> paths;
 paths.push_back(MYGAME_PYTHON_PATH);
 // +any other Python paths...
-entityx::ptr<PythonSystem> script_system = new PythonSystem(paths);
+ptr<PythonSystem> python(new PythonSystem(paths));
 
 // Add any Event proxies.
-script_system->add_event_proxy<CollisionEvent>(ev, new CollisionEventProxy());
+python->add_event_proxy<CollisionEvent>(ev, ptr<CollisionEventProxy>(new CollisionEventProxy()));
 ```
index 8f5057bd637e71687d0b0f05f2290ce8b41e8d29..339c90bdf5bef5ec88684d0b742d923eff583dc5 100644 (file)
@@ -40,11 +40,11 @@ class Component(object):
         self._args = args
         self._kwargs = kwargs
 
-    def _build(self, entity):
-        component = self._cls.get_component(entity)
+    def _build(self, entity_id):
+        component = self._cls.get_component(_entityx._entity_manager, entity_id)
         if not component:
             component = self._cls(*self._args, **self._kwargs)
-            component.assign_to(entity)
+            component.assign_to(_entityx._entity_manager, entity_id)
         return component
 
 
@@ -77,27 +77,28 @@ class Entity(_entityx.Entity):
     __metaclass__ = EntityMetaClass
 
     def __new__(cls, *args, **kwargs):
-        entity = kwargs.pop('raw_entity', None)
+        entity_id = kwargs.pop('entity_id', None)
         self = _entityx.Entity.__new__(cls)
-        if entity is None:
-            entity = _entityx._entity_manager.create()
-            component = _entityx.PythonComponent(self)
-            component.assign_to(entity)
-        _entityx.Entity.__init__(self, entity)
+        if entity_id is None:
+            entity_id = _entityx._entity_manager.configure(self)
+        _entityx.Entity.__init__(self, _entityx._entity_manager, entity_id)
         for k, v in self._components.items():
-            setattr(self, k, v._build(self._entity))
+            setattr(self, k, v._build(self._entity_id))
         return self
 
     def __init__(self):
         """Default constructor."""
 
+    def __repr__(self):
+        return '<%s.%s %d.%d>' % (self.__class__.__module__, self.__class__.__name__, self._entity_id.index, self._entity_id.version)
+
     @classmethod
-    def _from_raw_entity(cls, raw_entity, *args, **kwargs):
+    def _from_raw_entity(cls, entity_id, *args, **kwargs):
         """Create a new Entity from a raw entity.
 
         This is called from C++.
         """
-        self = Entity.__new__(cls, raw_entity=raw_entity)
+        self = Entity.__new__(cls, entity_id=entity_id)
         cls.__init__(self, *args, **kwargs)
         return self
 
index e71fa2160bfb889869bdd864b22eca116f0c1eb6..8f8f3a44ee3b2653a30d4093cea22f5ed399f3c8 100644 (file)
@@ -14,19 +14,19 @@ class EntityA(entityx.Entity):
 
 def create_entities_from_python_test():
     a = EntityA()
-    assert a._entity.id & 0xffffffff == 0
+    assert a._entity_id.index == 0
     assert a.position.x == 1.0
     assert a.position.y == 2.0
 
     b = EntityA()
-    assert b._entity.id & 0xffffffff == 1
+    assert b._entity_id.index == 1
 
     a.destroy()
     c = EntityA()
     # Reuse destroyed index of "a".
-    assert c._entity.id & 0xffffffff == 0
+    assert c._entity_id.index == 0
     # However, version is different
-    assert a._entity.id != c._entity.id and c._entity.id > a._entity.id
+    assert a._entity_id.id != c._entity_id.id and c._entity_id.id > a._entity_id.id
 
     d = EntityA(2.0, 3.0)
     assert d.position.x == 2.0
index 9a726ba189f286fb8ec2a16409e225b9c3676ad5..4696e26e7f65f78b65be308ec2ef59f3f4485592 100644 (file)
@@ -10,5 +10,4 @@ def emit_collision_from_python():
     a = AnEntity()
     b = AnEntity()
     collision = Collision(a, b)
-    print a, b, collision
-    emit(Collision(a, b))
+    emit(collision)
index f73cf88abf5935849703e9f44f35f8ec61ae4e95..8edf8c58452892a9a97cbcb29f3d08f2a1683f8b 100644 (file)
@@ -7,4 +7,5 @@ class EventTest(Entity):
     def on_collision(self, event):
         assert event.a
         assert event.b
+        assert event.a == self or event.b == self
         self.collided = True