1#ifdef NANOGUI_PYTHON
2
3#include <thread>
4#include <mutex>
5#include <condition_variable>
6#include "python.h"
7
8#if defined(__APPLE__) || defined(__linux__)
9# include <coro.h>
10# include <signal.h>
11#endif
12
13#if defined(__APPLE__) || defined(__linux__)
14namespace {
15 class semaphore {
16 public:
17 semaphore(int count = 0) : count(count) { }
18
19 void notify() {
20 std::unique_lock<std::mutex> lck(mtx);
21 ++count;
22 cv.notify_one();
23 }
24
25 void wait() {
26 std::unique_lock<std::mutex> lck(mtx);
27 while (count == 0)
28 cv.wait(lck);
29 --count;
30 }
31
32 private:
33 std::mutex mtx;
34 std::condition_variable cv;
35 int count;
36 };
37}
38#endif
39
40extern void register_constants_glfw(py::module &m);
41extern void register_constants_entypo(py::module &m);
42extern void register_eigen(py::module &m);
43extern void register_widget(py::module &m);
44extern void register_layout(py::module &m);
45extern void register_basics(py::module &m);
46extern void register_button(py::module &m);
47extern void register_tabs(py::module &m);
48extern void register_textbox(py::module &m);
49extern void register_theme(py::module &m);
50extern void register_glcanvas(py::module &m);
51extern void register_formhelper(py::module &m);
52extern void register_misc(py::module &m);
53extern void register_glutil(py::module &m);
54extern void register_nanovg(py::module &m);
55
56class MainloopHandle;
57static MainloopHandle *handle = nullptr;
58
59class MainloopHandle {
60public:
61 bool active = false;
62 bool detached = false;
63 int refresh = 0;
64 std::thread thread;
65
66 #if defined(__APPLE__) || defined(__linux__)
67 coro_context ctx_helper, ctx_main, ctx_thread;
68 coro_stack stack;
69 semaphore sema;
70 #endif
71
72 ~MainloopHandle() {
73 join();
74 handle = nullptr;
75 }
76
77 void join() {
78 if (!detached)
79 return;
80
81 #if defined(__APPLE__) || defined(__linux__)
82 /* Release GIL and disassociate from thread state (which was originally
83 associated with the main Python thread) */
84 py::gil_scoped_release thread_state(true);
85
86 coro_transfer(&ctx_main, &ctx_thread);
87 coro_stack_free(&stack);
88
89 /* Destroy the thread state that was created in mainloop() */
90 {
91 py::gil_scoped_acquire acquire;
92 acquire.dec_ref();
93 }
94 #endif
95
96 thread.join();
97 detached = false;
98
99 #if defined(__APPLE__) || defined(__linux__)
100 /* Reacquire GIL and reassociate with thread state
101 [via RAII destructor in 'thread_state'] */
102 #endif
103 }
104};
105
106#if defined(__APPLE__) || defined(__linux__)
107static void (*sigint_handler_prev)(int) = nullptr;
108static void sigint_handler(int sig) {
109 nanogui::leave();
110 signal(sig, sigint_handler_prev);
111 raise(sig);
112}
113#endif
114
115PYBIND11_MODULE(nanogui, m) {
116 m.attr("__doc__") = "NanoGUI plugin";
117
118 py::class_<MainloopHandle>(m, "MainloopHandle")
119 .def("join", &MainloopHandle::join);
120
121 m.def("init", &nanogui::init, D(init));
122 m.def("shutdown", &nanogui::shutdown, D(shutdown));
123 m.def("mainloop", [](int refresh, py::object detach) -> MainloopHandle* {
124 if (!detach.is(py::none())) {
125 if (handle)
126 throw std::runtime_error("Main loop is already running!");
127
128 handle = new MainloopHandle();
129 handle->detached = true;
130 handle->refresh = refresh;
131
132 #if defined(__APPLE__) || defined(__linux__)
133 /* Release GIL and completely disassociate the calling thread
134 from its associated Python thread state data structure */
135 py::gil_scoped_release thread_state(true);
136
137 /* Create a new thread state for the nanogui main loop
138 and reference it once (to keep it from being constructed and
139 destructed at every callback invocation) */
140 {
141 py::gil_scoped_acquire acquire;
142 acquire.inc_ref();
143 }
144
145 handle->thread = std::thread([]{
146 /* Handshake 1: wait for signal from detach_helper */
147 handle->sema.wait();
148
149 /* Swap context with main thread */
150 coro_transfer(&handle->ctx_thread, &handle->ctx_main);
151
152 /* Handshake 2: wait for signal from detach_helper */
153 handle->sema.notify();
154 });
155
156 void (*detach_helper)(void *) = [](void *ptr) -> void {
157 MainloopHandle *handle = (MainloopHandle *) ptr;
158
159 /* Handshake 1: Send signal to new thread */
160 handle->sema.notify();
161
162 /* Enter main loop */
163 sigint_handler_prev = signal(SIGINT, sigint_handler);
164 mainloop(handle->refresh);
165 signal(SIGINT, sigint_handler_prev);
166
167 /* Handshake 2: Wait for signal from new thread */
168 handle->sema.wait();
169
170 /* Return back to Python */
171 coro_transfer(&handle->ctx_helper, &handle->ctx_main);
172 };
173
174 /* Allocate an 8MB stack and transfer context to the
175 detach_helper function */
176 coro_stack_alloc(&handle->stack, 8 * 1024 * 1024);
177 coro_create(&handle->ctx_helper, detach_helper, handle,
178 handle->stack.sptr, handle->stack.ssze);
179 coro_transfer(&handle->ctx_main, &handle->ctx_helper);
180 #else
181 handle->thread = std::thread([]{
182 mainloop(handle->refresh);
183 });
184 #endif
185
186 #if defined(__APPLE__) || defined(__linux__)
187 /* Reacquire GIL and reassociate with thread state on newly
188 created thread [via RAII destructor in 'thread_state'] */
189 #endif
190
191 return handle;
192 } else {
193 py::gil_scoped_release release;
194
195 #if defined(__APPLE__) || defined(__linux__)
196 sigint_handler_prev = signal(SIGINT, sigint_handler);
197 #endif
198
199 mainloop(refresh);
200
201 #if defined(__APPLE__) || defined(__linux__)
202 signal(SIGINT, sigint_handler_prev);
203 #endif
204
205 return nullptr;
206 }
207 }, py::arg("refresh") = 50, py::arg("detach") = py::none(),
208 D(mainloop), py::keep_alive<0, 2>());
209
210 m.def("leave", &nanogui::leave, D(leave));
211 m.def("active", &nanogui::active, D(active));
212 m.def("file_dialog", (std::string(*)(const std::vector<std::pair<std::string, std::string>> &, bool)) &nanogui::file_dialog, D(file_dialog));
213 m.def("file_dialog", (std::vector<std::string>(*)(const std::vector<std::pair<std::string, std::string>> &, bool, bool)) &nanogui::file_dialog, D(file_dialog, 2));
214 #if defined(__APPLE__)
215 m.def("chdir_to_bundle_parent", &nanogui::chdir_to_bundle_parent);
216 #endif
217 m.def("utf8", [](int c) { return std::string(utf8(c).data()); }, D(utf8));
218 m.def("loadImageDirectory", &nanogui::loadImageDirectory, D(loadImageDirectory));
219
220 py::enum_<Cursor>(m, "Cursor", D(Cursor))
221 .value("Arrow", Cursor::Arrow)
222 .value("IBeam", Cursor::IBeam)
223 .value("Crosshair", Cursor::Crosshair)
224 .value("Hand", Cursor::Hand)
225 .value("HResize", Cursor::HResize)
226 .value("VResize", Cursor::VResize);
227
228 py::enum_<Alignment>(m, "Alignment", D(Alignment))
229 .value("Minimum", Alignment::Minimum)
230 .value("Middle", Alignment::Middle)
231 .value("Maximum", Alignment::Maximum)
232 .value("Fill", Alignment::Fill);
233
234 py::enum_<Orientation>(m, "Orientation", D(Orientation))
235 .value("Horizontal", Orientation::Horizontal)
236 .value("Vertical", Orientation::Vertical);
237
238 register_constants_glfw(m);
239 register_constants_entypo(m);
240 register_eigen(m);
241 register_widget(m);
242 register_layout(m);
243 register_basics(m);
244 register_button(m);
245 register_tabs(m);
246 register_textbox(m);
247 register_theme(m);
248 register_glcanvas(m);
249 register_formhelper(m);
250 register_misc(m);
251 register_glutil(m);
252 register_nanovg(m);
253}
254
255#endif
256