diff options
author | Alec Thomas <alec@swapoff.org> | 2013-12-20 00:24:01 -0500 |
---|---|---|
committer | Alec Thomas <alec@swapoff.org> | 2013-12-20 00:24:01 -0500 |
commit | d74c47d8a9c3acbdc8fdd6ff3712c66f4f90b7d5 (patch) | |
tree | c26b774c409d2fa68606aa5aac9ac1b054618679 | |
parent | dad637cdec9c24b34ad430aee0e55553d6ea238d (diff) |
Event delivery to/from Python and C++.
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | entityx/Entity.cc | 4 | ||||
-rw-r--r-- | entityx/Entity.h | 30 | ||||
-rw-r--r-- | entityx/python/PythonSystem.cc | 88 | ||||
-rw-r--r-- | entityx/python/PythonSystem.h | 11 | ||||
-rw-r--r-- | entityx/python/PythonSystem_test.cc | 18 | ||||
-rw-r--r-- | entityx/python/README.md | 36 | ||||
-rw-r--r-- | entityx/python/entityx/__init__.py | 25 | ||||
-rw-r--r-- | entityx/python/entityx/tests/create_entities_from_python_test.py | 8 | ||||
-rw-r--r-- | entityx/python/entityx/tests/event_emit_test.py | 3 | ||||
-rw-r--r-- | entityx/python/entityx/tests/event_test.py | 1 |
12 files changed, 144 insertions, 83 deletions
@@ -6,3 +6,5 @@ build/* entityx/config.h Makefile html/ +Vagrantfile +.vagrant diff --git a/CMakeLists.txt b/CMakeLists.txt index 104efb1..f49c75f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/entityx/Entity.cc b/entityx/Entity.cc index dfc6bb5..48b5827 100644 --- a/entityx/Entity.cc +++ b/entityx/Entity.cc @@ -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) { diff --git a/entityx/Entity.h b/entityx/Entity.h index ba1822a..9577375 100644 --- a/entityx/Entity.h +++ b/entityx/Entity.h @@ -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 diff --git a/entityx/python/PythonSystem.cc b/entityx/python/PythonSystem.cc index 220003b..373948d 100644 --- a/entityx/python/PythonSystem.cc +++ b/entityx/python/PythonSystem.cc @@ -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)); } diff --git a/entityx/python/PythonSystem.h b/entityx/python/PythonSystem.h index 3009c6c..d63fa38 100644 --- a/entityx/python/PythonSystem.h +++ b/entityx/python/PythonSystem.h @@ -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); } diff --git a/entityx/python/PythonSystem_test.cc b/entityx/python/PythonSystem_test.cc index 1d0c8fd..d4d531c 100644 --- a/entityx/python/PythonSystem_test.cc +++ b/entityx/python/PythonSystem_test.cc @@ -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); diff --git a/entityx/python/README.md b/entityx/python/README.md index e206a05..cf83ab5 100644 --- a/entityx/python/README.md +++ b/entityx/python/README.md @@ -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())); ``` diff --git a/entityx/python/entityx/__init__.py b/entityx/python/entityx/__init__.py index 8f5057b..339c90b 100644 --- a/entityx/python/entityx/__init__.py +++ b/entityx/python/entityx/__init__.py @@ -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 diff --git a/entityx/python/entityx/tests/create_entities_from_python_test.py b/entityx/python/entityx/tests/create_entities_from_python_test.py index e71fa21..8f8f3a4 100644 --- a/entityx/python/entityx/tests/create_entities_from_python_test.py +++ b/entityx/python/entityx/tests/create_entities_from_python_test.py @@ -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 diff --git a/entityx/python/entityx/tests/event_emit_test.py b/entityx/python/entityx/tests/event_emit_test.py index 9a726ba..4696e26 100644 --- a/entityx/python/entityx/tests/event_emit_test.py +++ b/entityx/python/entityx/tests/event_emit_test.py @@ -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) diff --git a/entityx/python/entityx/tests/event_test.py b/entityx/python/entityx/tests/event_test.py index f73cf88..8edf8c5 100644 --- a/entityx/python/entityx/tests/event_test.py +++ b/entityx/python/entityx/tests/event_test.py @@ -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 |