/*
* Copyright (C) 2013 Alec Thomas
* 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
*/
#pragma once
// http://docs.python.org/2/extending/extending.html
#include
#include
#include
#include
#include
#include "entityx/System.h"
#include "entityx/Entity.h"
#include "entityx/Event.h"
namespace entityx {
namespace python {
/**
* An EntityX component that represents a Python script.
*/
class PythonScript {
public:
/**
* Create a new PythonScript from a Python Entity class.
*
* @param module The Python module where the Entity subclass resides.
* @param cls The Class within the module. Must inherit from entityx.Entity.
* @param args The *args to pass to the Python constructor.
*/
template
PythonScript(const std::string &module, const std::string &cls, Args ... args) : module(module), cls(cls) {
unpack_args(args...);
}
/**
* Create a new PythonScript from an existing Python instance.
*/
explicit PythonScript(boost::python::object object) : object(object) {}
boost::python::object object;
boost::python::list args;
const std::string module, cls;
private:
template
void unpack_args(A &arg, Args ... remainder) {
args.append(arg);
unpack_args(remainder...);
}
void unpack_args() {}
};
class PythonSystem;
/**
* Proxies C++ EntityX events to Python entities.
*/
class PythonEventProxy {
public:
friend class PythonSystem;
/**
* Construct a new event proxy.
*
* @param handler_name The default implementation of can_send() tests for
* the existence of this attribute on an Entity.
*/
explicit PythonEventProxy(const std::string &handler_name) : handler_name(handler_name) {}
virtual ~PythonEventProxy() {}
/**
* Return true if this event can be sent to the provided Python entity.
*
* @param object The Python entity to test for event delivery.
*/
virtual bool can_send(const boost::python::object &object) const {
return PyObject_HasAttrString(object.ptr(), handler_name.c_str());
}
protected:
std::list entities;
const std::string handler_name;
private:
/**
* Add an Entity receiver to this proxy. This is called automatically by PythonSystem.
*
* @param entity The entity that will receive events.
*/
void add_receiver(Entity entity) {
entities.push_back(entity);
}
/**
* Delete an Entity receiver. This is called automatically by PythonSystem.
*
* @param entity The entity that was receiving events.
*/
void delete_receiver(Entity entity) {
for ( auto i = entities.begin(); i != entities.end(); ++i ) {
if ( entity == *i ) {
entities.erase(i);
break;
}
}
}
};
/**
* A helper function for class_ to assign a component to an entity.
*/
template
void assign_to(Component& component, EntityManager& entity_manager, Entity::Id id) {
entity_manager.assign(id, component);
}
/**
* A helper function for retrieving an existing component associated with an
* entity.
*/
template
static Component* get_component(EntityManager& em, Entity::Id id) {
auto handle = em.component(id);
if ( !handle )
return NULL;
return handle.get();
}
/**
* A PythonEventProxy that broadcasts events to all entities with a matching
* handler method.
*/
template
class BroadcastPythonEventProxy : public PythonEventProxy, public Receiver> {
public:
BroadcastPythonEventProxy(const std::string &handler_name) : PythonEventProxy(handler_name) {}
virtual ~BroadcastPythonEventProxy() {}
void receive(const Event &event) {
for ( auto entity : entities ) {
auto py_entity = entity.template component();
py_entity->object.attr(handler_name.c_str())(event);
}
}
};
/**
* An entityx::System that bridges EntityX and Python.
*
* This system handles exposing entityx functionality to Python. The Python
* support differs in design from the C++ design in the following ways:
*
* - Entities contain logic and can receive events.
* - Systems and Components can not be implemented in Python.
*/
class PythonSystem : public entityx::System, public entityx::Receiver {
public:
typedef std::function LoggerFunction;
PythonSystem(EntityManager& entity_manager); // NOLINT
virtual ~PythonSystem();
/**
* Add system-installed entityx Python path to the interpreter.
*/
void add_installed_library_path();
/**
* Add a Python path to the interpreter.
*/
void add_path(const std::string &path);
/**
* Add a sequence of paths to the interpreter.
*/
template
void add_paths(const T &paths) {
for ( auto path : paths ) {
add_path(path);
}
}
/// Return the Python paths the system is configured with.
const std::vector<:string> &python_paths() const {
return python_paths_;
}
virtual void configure(EventManager& event_manager) override;
virtual void update(EntityManager& entities, EventManager& event_manager, TimeDelta dt) override;
/**
* Set line-based (not including \n) logger for stdout and stderr.
*/
void log_to(LoggerFunction sout, LoggerFunction serr);
/**
* Proxy events of type Event to any Python entity with a handler_name method.
*/
template
void add_event_proxy(EventManager& event_manager, const std::string &handler_name) {
std::shared_ptr> proxy(new BroadcastPythonEventProxy(handler_name));
event_manager.subscribe(*proxy.get());
event_proxies_.push_back(std::static_pointer_cast(proxy));
}
/**
* Proxy events of type Event using the given PythonEventProxy implementation.
*/
template
void add_event_proxy(EventManager& event_manager, std::shared_ptr proxy) {
event_manager.subscribe(*proxy);
event_proxies_.push_back(std::static_pointer_cast(proxy));
}
void receive(const EntityDestroyedEvent &event);
void receive(const ComponentAddedEvent &event);
private:
void initialize_python_module();
EntityManager& em_;
std::vector<:string> python_paths_;
LoggerFunction stdout_, stderr_;
static bool initialized_;
std::vector<:shared_ptr>> event_proxies_;
};
} // namespace python
} // namespace entityx