{"id":16092322,"url":"https://github.com/alecthomas/entityx_python","last_synced_at":"2025-03-18T06:30:59.666Z","repository":{"id":12680264,"uuid":"15352355","full_name":"alecthomas/entityx_python","owner":"alecthomas","description":"Python bindings for EntityX","archived":false,"fork":false,"pushed_at":"2023-12-02T01:43:42.000Z","size":333,"stargazers_count":14,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-28T07:51:12.390Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alecthomas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-12-21T01:58:21.000Z","updated_at":"2024-07-01T13:58:45.000Z","dependencies_parsed_at":"2024-10-27T17:39:57.057Z","dependency_job_id":null,"html_url":"https://github.com/alecthomas/entityx_python","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fentityx_python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fentityx_python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fentityx_python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alecthomas%2Fentityx_python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alecthomas","download_url":"https://codeload.github.com/alecthomas/entityx_python/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243909980,"owners_count":20367536,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-09T16:07:04.144Z","updated_at":"2025-03-18T06:30:59.056Z","avatar_url":"https://github.com/alecthomas.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Python Bindings for [EntityX](https://github.com/alecthomas/entityx) (α Alpha)\n\nThis 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.\n\n## Example\n\n```python\nfrom entityx import Entity, Component, emit\nfrom mygame import Position, Health, Dead\n\n\nclass Player(Entity):\n    position = Component(Position, 0, 0)\n    health = Component(Health, 100)\n\n    def on_collision(self, event):\n        self.health.health -= 10\n        if self.health.health \u003c= 0:\n            emit(Dead(self))\n\n```\n\n## Building and installing\n\nEntityX Python has the following build and runtime requirements:\n\n- [EntityX](https://github.com/alecthomas/entityx)\n- [Boost Python](https://boostorg.github.io/python/doc/html/index.html)\n- [CMake](http://cmake.org/)\n\n### CMake Options:\n\n- `ENTITYX_PYTHON_BUILD_TESTING` : Enable building of tests\n- `BOOST_ROOT` : Set path to boost root if CMake did not find it\n- `ENTITYX_ROOT` : Set path to EntityX root if CMake did not find it\n- `PYTHON_ROOT` : Set path to Python root if CMake did not find it\n\nCheck out the source to entityx_python, and run:\n\n```bash\nmkdir build \u0026\u0026 cd build\ncmake  ..\nmake\nmake install\n```\n\n## Design\n\n- Python scripts are attached to entities via `PythonScript`.\n- Systems and components can not be created from Python, primarily for performance reasons.\n- Events are proxied directly to Python entities via `PythonEventProxy` objects.\n    - Each event to be handled in Python must have an associated `PythonEventProxy`implementation.\n    - As a convenience `BroadcastPythonEventProxy\u003cEvent\u003e(handler_method)` can be used. It will broadcast events to all `PythonScript` entities with a `\u003chandler_method\u003e`.\n- `PythonSystem` manages scripted entity lifecycle and event delivery.\n\n## Summary\n\nTo add scripting support to your system, something like the following steps should be followed:\n\n1. Expose C++ `Component` and `Event` classes to Python with `BOOST_PYTHON_MODULE`.\n2. Initialize the module with `PyImport_AppendInittab`.\n3. Create a Python package.\n4. Add classes to the package, inheriting from `entityx.Entity` and using the `entityx.Component` descriptor to assign components.\n5. Create a `PythonSystem`, passing in the list of paths to add to Python's import search path.\n6. Optionally attach any event proxies.\n7. Create an entity and associate it with a Python script by assigning `PythonScript`, passing it the package name, class name, and any constructor arguments.\n8. When finished, call `EntityManager::destroy_all()`.\n\n## Interfacing with Python\n\n`entityx::python` primarily uses standard `boost::python` to interface with Python, with some helper classes and functions.\n\n### Exposing C++ Components to Python\n\nIn 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\u003cComponent\u003e` and `get_component\u003cComponent\u003e`. These are used from Python to assign Python-created components to an entity and to retrieve existing components from an entity, respectively.\n\nHere's an example:\n\n```c++\nnamespace py = boost::python;\n\nstruct Position : public entityx::Component\u003cPosition\u003e {\n  Position(float x = 0.0, float y = 0.0) : x(x), y(y) {}\n\n  float x, y;\n};\n\nvoid export_position_to_python() {\n  py::class_\u003cPosition\u003e(\"Position\", py::init\u003cpy::optional\u003cfloat, float\u003e\u003e())\n    // Allows this component to be assigned to an entity\n    .def(\"assign_to\", \u0026entityx::python::assign_to\u003cPosition\u003e)\n    // Allows this component to be retrieved from an entity.\n    // Set return_value_policy to reference raw component pointer\n    .def(\"get_component\", \u0026entityx::python::get_component\u003cPosition\u003e,\n         py::return_value_policy\u003cpy::reference_existing_object\u003e() )\n    .staticmethod(\"get_component\")\n    .def_readwrite(\"x\", \u0026Position::x)\n    .def_readwrite(\"y\", \u0026Position::y);\n}\n\nBOOST_PYTHON_MODULE(mygame) {\n  export_position_to_python();\n}\n```\n\n### Using C++ Components from Python\n\nUse the `entityx.Component` class descriptor to associate components and provide default constructor arguments:\n\n```python\nimport entityx\nfrom mygame import Position  # C++ Component\n\nclass MyEntity(entityx.Entity):\n    # Ensures MyEntity has an associated Position component,\n    # constructed with the given arguments.\n    position = entityx.Component(Position, 1, 2)\n\n    def __init__(self):\n        assert self.position.x == 1\n        assert self.position.y == 2\n```\n\n### Delivering events to Python entities\n\nUnlike in C++, where events are typically handled by systems, EntityX::Python\nexplicitly provides support for sending events to entities. To bridge this gap\nuse the `PythonEventProxy` class to receive C++ events and proxy them to\nPython entities.\n\nThe class takes a single parameter, which is the name of the attribute on a\nPython entity. If this attribute exists, the entity will be added to\n`PythonEventProxy::entities (std::list\u003cEntity\u003e)`, so that matching entities\nwill be accessible from any event handlers.\n\nThis checking is performed in `PythonEventProxy::can_send()`, and can be\noverridden, but further checking can also be done in the event `receive()`\nmethod.\n\nA helper template class called `BroadcastPythonEventProxy\u003cEvent\u003e` is provided\nthat will broadcast events to any entity with the corresponding handler method.\n\nTo implement more refined logic, subclass `PythonEventProxy` and operate on\nthe protected member `entities`. Here's a collision example, where the proxy\nonly delivers collision events to the colliding entities themselves:\n\n```c++\nstruct CollisionEvent : public entityx::Event\u003cCollisionEvent\u003e {\n  CollisionEvent(Entity a, Entity b) : a(a), b(b) {}\n\n  // NOTE: See note below in export_collision_event_to_python().\n  Entity a, b;\n};\n\nstruct CollisionEventProxy : public entityx::python::PythonEventProxy, public entityx::Receiver\u003cCollisionEvent\u003e {\n  CollisionEventProxy() : entityx::python::PythonEventProxy(\"on_collision\") {}\n\n  void receive(const CollisionEvent \u0026event) {\n    // \"entities\" is a protected data member, populated by\n    // PythonSystem, with Python entities that pass can_send().\n    for (auto entity : entities) {\n      auto py_entity = entity.template component\u003centityx::python::PythonComponent\u003e();\n      if (entity == event.a || entity == event.b) {\n        py_entity-\u003eobject.attr(handler_name.c_str())(event);\n      }\n    }\n  }\n};\n\nvoid export_collision_event_to_python() {\n  py::class_\u003cCollisionEvent\u003e(\"Collision\", py::init\u003cEntity, Entity\u003e())\n    // NOTE: Normally, def_readonly() would be used to expose attributes,\n    // but you must use the following construct in order for Entity\n    // objects to be automatically converted into their Python instances.\n    .add_property(\"a\", py::make_getter(\u0026CollisionEvent::a, py::return_value_policy\u003cpy::return_by_value\u003e()))\n    .add_property(\"b\", py::make_getter(\u0026CollisionEvent::b, py::return_value_policy\u003cpy::return_by_value\u003e()));\n\n  // Register event manager emit so signal handlers will trigger properly\n  void (EventManager::*emit)(const CollisionEvent \u0026) = \u0026EventManager::emit;\n\n  py::class_\u003cEventManager, boost::noncopyable\u003e(\"EventManager\", py::no_init)\n    .def(\"emit\", emit);\n}\n\n\nBOOST_PYTHON_MODULE(mygame) {\n  export_position_to_python();\n  export_collision_event_to_python();\n}\n```\n\n\n### Sending events from Python\n\nThis is relatively straight forward. Once you have exported a C++ event to Python:\n\n```python\nfrom entityx import Entity, emit\nfrom mygame import Collision\n\n\nclass AnEntity(Entity): pass\n\n\nemit(Collision(AnEntity(), AnEntity()))\n```\n\n\n### Initialization\n\nFinally, initialize the `mygame` module once, before using `PythonSystem`, with something like this:\n\n```c++\n// This should only be performed once, at application initialization time.\nCHECK(PyImport_AppendInittab(\"mygame\", initmygame) != -1)\n  \u003c\u003c \"Failed to initialize mygame Python module\";\n```\n\nThen create a `PythonSystem` as necessary:\n\n```c++\n// Initialize the PythonSystem.\nvector\u003cstring\u003e paths;\n// Ensure that MYGAME_PYTHON_PATH includes entityx.py from this distribution.\npaths.push_back(MYGAME_PYTHON_PATH);\n// +any other Python paths...\nentityx::python::PythonSystem python(paths);\n\n// Add any Event proxies.\npython-\u003eadd_event_proxy\u003cCollisionEvent\u003e(ev, std::make_shared\u003cCollisionEventProxy\u003e());\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fentityx_python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falecthomas%2Fentityx_python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falecthomas%2Fentityx_python/lists"}