Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/alecthomas/entityx_python
Python bindings for EntityX
https://github.com/alecthomas/entityx_python
Last synced: 16 days ago
JSON representation
Python bindings for EntityX
- Host: GitHub
- URL: https://github.com/alecthomas/entityx_python
- Owner: alecthomas
- Created: 2013-12-21T01:58:21.000Z (almost 11 years ago)
- Default Branch: master
- Last Pushed: 2023-12-02T01:43:42.000Z (12 months ago)
- Last Synced: 2024-10-10T16:06:51.503Z (about 1 month ago)
- Language: C++
- Size: 325 KB
- Stars: 14
- Watchers: 4
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Python Bindings for [EntityX](https://github.com/alecthomas/entityx) (α Alpha)
This system adds the ability to extend entity logic with Python scripts. The goal is to allow ad-hoc behaviour to be assigned to entities through scripts, in contrast to the more strictly pure entity-component system approach.
## Example
```python
from entityx import Entity, Component, emit
from mygame import Position, Health, Deadclass Player(Entity):
position = Component(Position, 0, 0)
health = Component(Health, 100)def on_collision(self, event):
self.health.health -= 10
if self.health.health <= 0:
emit(Dead(self))```
## Building and installing
EntityX Python has the following build and runtime requirements:
- [EntityX](https://github.com/alecthomas/entityx)
- [Boost Python](https://boostorg.github.io/python/doc/html/index.html)
- [CMake](http://cmake.org/)### CMake Options:
- `ENTITYX_PYTHON_BUILD_TESTING` : Enable building of tests
- `BOOST_ROOT` : Set path to boost root if CMake did not find it
- `ENTITYX_ROOT` : Set path to EntityX root if CMake did not find it
- `PYTHON_ROOT` : Set path to Python root if CMake did not find itCheck out the source to entityx_python, and run:
```bash
mkdir build && cd build
cmake ..
make
make install
```## Design
- Python scripts are attached to entities via `PythonScript`.
- Systems and components can not be created from Python, primarily for performance reasons.
- Events are proxied directly to Python entities via `PythonEventProxy` objects.
- Each event to be handled in Python must have an associated `PythonEventProxy`implementation.
- As a convenience `BroadcastPythonEventProxy(handler_method)` can be used. It will broadcast events to all `PythonScript` entities with a ``.
- `PythonSystem` manages scripted entity lifecycle and event delivery.## Summary
To add scripting support to your system, something like the following steps should be followed:
1. Expose C++ `Component` and `Event` classes to Python with `BOOST_PYTHON_MODULE`.
2. Initialize the module with `PyImport_AppendInittab`.
3. Create a Python package.
4. Add classes to the package, inheriting from `entityx.Entity` and using the `entityx.Component` descriptor to assign components.
5. Create a `PythonSystem`, passing in the list of paths to add to Python's import search path.
6. Optionally attach any event proxies.
7. Create an entity and associate it with a Python script by assigning `PythonScript`, passing it the package name, class name, and any constructor arguments.
8. When finished, call `EntityManager::destroy_all()`.## Interfacing with Python
`entityx::python` primarily uses standard `boost::python` to interface with Python, with some helper classes and functions.
### 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` and `get_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;struct Position : public entityx::Component {
Position(float x = 0.0, float y = 0.0) : x(x), y(y) {}float x, y;
};void export_position_to_python() {
py::class_("Position", py::init>())
// Allows this component to be assigned to an entity
.def("assign_to", &entityx::python::assign_to)
// Allows this component to be retrieved from an entity.
// Set return_value_policy to reference raw component pointer
.def("get_component", &entityx::python::get_component,
py::return_value_policy() )
.staticmethod("get_component")
.def_readwrite("x", &Position::x)
.def_readwrite("y", &Position::y);
}BOOST_PYTHON_MODULE(mygame) {
export_position_to_python();
}
```### Using C++ Components from Python
Use the `entityx.Component` class descriptor to associate components and provide default constructor arguments:
```python
import entityx
from mygame import Position # C++ Componentclass MyEntity(entityx.Entity):
# Ensures MyEntity has an associated Position component,
# constructed with the given arguments.
position = entityx.Component(Position, 1, 2)def __init__(self):
assert self.position.x == 1
assert self.position.y == 2
```### Delivering events to Python entities
Unlike in C++, where events are typically handled by systems, EntityX::Python
explicitly provides support for sending events to entities. To bridge this gap
use the `PythonEventProxy` class to receive C++ events and proxy them to
Python entities.The class takes a single parameter, which is the name of the attribute on a
Python entity. If this attribute exists, the entity will be added to
`PythonEventProxy::entities (std::list)`, so that matching entities
will be accessible from any event handlers.This checking is performed in `PythonEventProxy::can_send()`, and can be
overridden, but further checking can also be done in the event `receive()`
method.A helper template class called `BroadcastPythonEventProxy` is provided
that will broadcast events to any entity with the corresponding handler method.To implement more refined logic, subclass `PythonEventProxy` and operate on
the protected member `entities`. Here's a collision example, where the proxy
only delivers collision events to the colliding entities themselves:```c++
struct CollisionEvent : public entityx::Event {
CollisionEvent(Entity a, Entity b) : a(a), b(b) {}// NOTE: See note below in export_collision_event_to_python().
Entity a, b;
};struct CollisionEventProxy : public entityx::python::PythonEventProxy, public entityx::Receiver {
CollisionEventProxy() : entityx::python::PythonEventProxy("on_collision") {}void receive(const CollisionEvent &event) {
// "entities" is a protected data member, populated by
// PythonSystem, with Python entities that pass can_send().
for (auto entity : entities) {
auto py_entity = entity.template component();
if (entity == event.a || entity == event.b) {
py_entity->object.attr(handler_name.c_str())(event);
}
}
}
};void export_collision_event_to_python() {
py::class_("Collision", py::init())
// NOTE: Normally, def_readonly() would be used to expose attributes,
// but you must use the following construct in order for Entity
// objects to be automatically converted into their Python instances.
.add_property("a", py::make_getter(&CollisionEvent::a, py::return_value_policy()))
.add_property("b", py::make_getter(&CollisionEvent::b, py::return_value_policy()));// Register event manager emit so signal handlers will trigger properly
void (EventManager::*emit)(const CollisionEvent &) = &EventManager::emit;py::class_("EventManager", py::no_init)
.def("emit", emit);
}BOOST_PYTHON_MODULE(mygame) {
export_position_to_python();
export_collision_event_to_python();
}
```### 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 Collisionclass AnEntity(Entity): pass
emit(Collision(AnEntity(), AnEntity()))
```### Initialization
Finally, initialize the `mygame` module once, before using `PythonSystem`, with something like this:
```c++
// This should only be performed once, at application initialization time.
CHECK(PyImport_AppendInittab("mygame", initmygame) != -1)
<< "Failed to initialize mygame Python module";
```Then create a `PythonSystem` as necessary:
```c++
// Initialize the PythonSystem.
vector paths;
// Ensure that MYGAME_PYTHON_PATH includes entityx.py from this distribution.
paths.push_back(MYGAME_PYTHON_PATH);
// +any other Python paths...
entityx::python::PythonSystem python(paths);// Add any Event proxies.
python->add_event_proxy(ev, std::make_shared());
```