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__) |
14 | namespace { |
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 | |
40 | extern void register_constants_glfw(py::module &m); |
41 | extern void register_constants_entypo(py::module &m); |
42 | extern void register_eigen(py::module &m); |
43 | extern void register_widget(py::module &m); |
44 | extern void register_layout(py::module &m); |
45 | extern void register_basics(py::module &m); |
46 | extern void register_button(py::module &m); |
47 | extern void register_tabs(py::module &m); |
48 | extern void register_textbox(py::module &m); |
49 | extern void register_theme(py::module &m); |
50 | extern void register_glcanvas(py::module &m); |
51 | extern void register_formhelper(py::module &m); |
52 | extern void register_misc(py::module &m); |
53 | extern void register_glutil(py::module &m); |
54 | extern void register_nanovg(py::module &m); |
55 | |
56 | class MainloopHandle; |
57 | static MainloopHandle *handle = nullptr; |
58 | |
59 | class MainloopHandle { |
60 | public: |
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__) |
107 | static void (*sigint_handler_prev)(int) = nullptr; |
108 | static void sigint_handler(int sig) { |
109 | nanogui::leave(); |
110 | signal(sig, sigint_handler_prev); |
111 | raise(sig); |
112 | } |
113 | #endif |
114 | |
115 | PYBIND11_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 | |