2022-04-07 18:46:57 +02:00

272 lines
8.6 KiB
C++

#ifdef NANOGUI_PYTHON
#include "python.h"
#include <thread>
#include <mutex>
#include <condition_variable>
#if defined(__APPLE__) || defined(__linux__)
# include <coro.h>
# include <signal.h>
#endif
#if defined(__APPLE__) || defined(__linux__)
namespace {
class semaphore {
public:
semaphore(int count = 0) : count(count) { }
void notify() {
std::unique_lock<std::mutex> lck(mtx);
++count;
cv.notify_one();
}
void wait() {
std::unique_lock<std::mutex> lck(mtx);
while (count == 0)
cv.wait(lck);
--count;
}
private:
std::mutex mtx;
std::condition_variable cv;
int count;
};
}
#endif
extern void register_vector(py::module &m);
extern void register_glfw(py::module &m);
extern void register_entypo(py::module &m);
extern void register_eigen(py::module &m);
extern void register_widget(py::module &m);
extern void register_layout(py::module &m);
extern void register_basics(py::module &m);
extern void register_button(py::module &m);
extern void register_tabs(py::module &m);
extern void register_textbox(py::module &m);
extern void register_textarea(py::module &m);
extern void register_theme(py::module &m);
extern void register_canvas(py::module &m);
extern void register_formhelper(py::module &m);
extern void register_misc(py::module &m);
extern void register_nanovg(py::module &m);
extern void register_render(py::module &m);
class MainloopHandle;
static MainloopHandle *handle = nullptr;
class MainloopHandle {
public:
bool active = false;
bool detached = false;
float refresh = 0;
std::thread thread;
#if defined(__APPLE__) || defined(__linux__)
coro_context ctx_helper, ctx_main, ctx_thread;
coro_stack stack;
semaphore sema;
#endif
~MainloopHandle() {
join();
handle = nullptr;
}
void join() {
if (!detached)
return;
#if defined(__APPLE__) || defined(__linux__)
/* Release GIL and disassociate from thread state (which was originally
associated with the main Python thread) */
py::gil_scoped_release thread_state(true);
coro_transfer(&ctx_main, &ctx_thread);
coro_stack_free(&stack);
/* Destroy the thread state that was created in mainloop() */
{
py::gil_scoped_acquire acquire;
acquire.dec_ref();
}
#endif
thread.join();
detached = false;
#if defined(__APPLE__) || defined(__linux__)
/* Reacquire GIL and reassociate with thread state
[via RAII destructor in 'thread_state'] */
#endif
}
};
#if defined(__APPLE__) || defined(__linux__)
static void (*sigint_handler_prev)(int) = nullptr;
static void sigint_handler(int sig) {
nanogui::leave();
signal(sig, sigint_handler_prev);
raise(sig);
}
#endif
PYBIND11_MODULE(nanogui, m) {
m.attr("__doc__") = "NanoGUI plugin";
#if defined(NANOGUI_USE_OPENGL)
m.attr("api") = "opengl";
#elif defined(NANOGUI_USE_GLES) && NANOGUI_GLES_VERSION == 2
m.attr("api") = "gles2";
#elif defined(NANOGUI_USE_GLES) && NANOGUI_GLES_VERSION == 3
m.attr("api") = "gles3";
#elif defined(NANOGUI_USE_METAL)
m.attr("api") = "metal";
#endif
py::class_<MainloopHandle>(m, "MainloopHandle")
.def("join", &MainloopHandle::join);
m.def("init", &nanogui::init, D(init));
m.def("shutdown", &nanogui::shutdown, D(shutdown));
m.def("mainloop", [](float refresh, py::object detach) -> MainloopHandle* {
if (!detach.is(py::none())) {
if (handle)
throw std::runtime_error("Main loop is already running!");
handle = new MainloopHandle();
handle->detached = true;
handle->refresh = refresh;
#if defined(__APPLE__) || defined(__linux__)
/* Release GIL and completely disassociate the calling thread
from its associated Python thread state data structure */
py::gil_scoped_release thread_state(true);
/* Create a new thread state for the nanogui main loop
and reference it once (to keep it from being constructed and
destructed at every callback invocation) */
{
py::gil_scoped_acquire acquire;
acquire.inc_ref();
}
handle->thread = std::thread([]{
/* Handshake 1: wait for signal from detach_helper */
handle->sema.wait();
/* Swap context with main thread */
coro_transfer(&handle->ctx_thread, &handle->ctx_main);
/* Handshake 2: wait for signal from detach_helper */
handle->sema.notify();
});
void (*detach_helper)(void *) = [](void *ptr) -> void {
MainloopHandle *handle = (MainloopHandle *) ptr;
/* Handshake 1: Send signal to new thread */
handle->sema.notify();
/* Enter main loop */
sigint_handler_prev = signal(SIGINT, sigint_handler);
mainloop(handle->refresh);
signal(SIGINT, sigint_handler_prev);
/* Handshake 2: Wait for signal from new thread */
handle->sema.wait();
/* Return back to Python */
coro_transfer(&handle->ctx_helper, &handle->ctx_main);
};
/* Allocate an 8MB stack and transfer context to the
detach_helper function */
coro_stack_alloc(&handle->stack, 8 * 1024 * 1024);
coro_create(&handle->ctx_helper, detach_helper, handle,
handle->stack.sptr, handle->stack.ssze);
coro_transfer(&handle->ctx_main, &handle->ctx_helper);
#else
handle->thread = std::thread([]{
mainloop(handle->refresh);
});
#endif
#if defined(__APPLE__) || defined(__linux__)
/* Reacquire GIL and reassociate with thread state on newly
created thread [via RAII destructor in 'thread_state'] */
#endif
return handle;
} else {
py::gil_scoped_release release;
#if defined(__APPLE__) || defined(__linux__)
sigint_handler_prev = signal(SIGINT, sigint_handler);
#endif
mainloop(refresh);
#if defined(__APPLE__) || defined(__linux__)
signal(SIGINT, sigint_handler_prev);
#endif
return nullptr;
}
}, "refresh"_a = -1, "detach"_a = py::none(),
D(mainloop), py::keep_alive<0, 2>());
m.def("async", &nanogui::async, D(async));
m.def("leave", &nanogui::leave, D(leave));
m.def("active", &nanogui::active, D(active));
m.def("file_dialog", (std::string(*)(const std::vector<std::pair<std::string, std::string>> &, bool)) &nanogui::file_dialog, D(file_dialog));
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));
#if defined(__APPLE__)
m.def("chdir_to_bundle_parent", &nanogui::chdir_to_bundle_parent);
#endif
m.def("utf8", [](int c) { return std::string(utf8(c).data()); }, D(utf8));
m.def("load_image_directory", &nanogui::load_image_directory, D(load_image_directory));
py::enum_<Cursor>(m, "Cursor", D(Cursor))
.value("Arrow", Cursor::Arrow)
.value("IBeam", Cursor::IBeam)
.value("Crosshair", Cursor::Crosshair)
.value("Hand", Cursor::Hand)
.value("HResize", Cursor::HResize)
.value("VResize", Cursor::VResize);
py::enum_<Alignment>(m, "Alignment", D(Alignment))
.value("Minimum", Alignment::Minimum)
.value("Middle", Alignment::Middle)
.value("Maximum", Alignment::Maximum)
.value("Fill", Alignment::Fill);
py::enum_<Orientation>(m, "Orientation", D(Orientation))
.value("Horizontal", Orientation::Horizontal)
.value("Vertical", Orientation::Vertical);
register_vector(m);
register_glfw(m);
register_entypo(m);
register_eigen(m);
register_widget(m);
register_layout(m);
register_basics(m);
register_button(m);
register_tabs(m);
register_textbox(m);
register_textarea(m);
register_theme(m);
register_canvas(m);
register_formhelper(m);
register_misc(m);
register_nanovg(m);
register_render(m);
}
#endif