-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPythonSystem.h
More file actions
236 lines (205 loc) · 6.61 KB
/
PythonSystem.h
File metadata and controls
236 lines (205 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
* Copyright (C) 2013 Alec Thomas <alec@swapoff.org>
* 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 <alec@swapoff.org>
*/
#pragma once
// http://docs.python.org/2/extending/extending.html
#include <boost/python.hpp>
#include <boost/function.hpp>
#include <list>
#include <vector>
#include <string>
#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 <typename ...Args>
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 <typename A, typename ...Args>
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<Entity> 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 <typename Component>
void assign_to(Component& component, EntityManager& entity_manager, Entity::Id id) {
entity_manager.assign<Component>(id, component);
}
/**
* A helper function for retrieving an existing component associated with an
* entity.
*/
template <typename Component>
static Component* get_component(EntityManager& em, Entity::Id id) {
auto handle = em.component<Component>(id);
if ( !handle )
return NULL;
return handle.get();
}
/**
* A PythonEventProxy that broadcasts events to all entities with a matching
* handler method.
*/
template <typename Event>
class BroadcastPythonEventProxy : public PythonEventProxy, public Receiver<BroadcastPythonEventProxy<Event>> {
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<PythonScript>();
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<PythonSystem>, public entityx::Receiver<PythonSystem> {
public:
typedef std::function<void(const std::string &)> 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 <typename T>
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<std::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 <typename Event>
void add_event_proxy(EventManager& event_manager, const std::string &handler_name) {
std::shared_ptr<BroadcastPythonEventProxy<Event>> proxy(new BroadcastPythonEventProxy<Event>(handler_name));
event_manager.subscribe<Event>(*proxy.get());
event_proxies_.push_back(std::static_pointer_cast<PythonEventProxy>(proxy));
}
/**
* Proxy events of type Event using the given PythonEventProxy implementation.
*/
template <typename Event, typename Proxy>
void add_event_proxy(EventManager& event_manager, std::shared_ptr<Proxy> proxy) {
event_manager.subscribe<Event>(*proxy);
event_proxies_.push_back(std::static_pointer_cast<PythonEventProxy>(proxy));
}
void receive(const EntityDestroyedEvent &event);
void receive(const ComponentAddedEvent<PythonScript> &event);
private:
void initialize_python_module();
EntityManager& em_;
std::vector<std::string> python_paths_;
LoggerFunction stdout_, stderr_;
static bool initialized_;
std::vector<std::shared_ptr<PythonEventProxy>> event_proxies_;
};
} // namespace python
} // namespace entityx