Is there way to have the message loop on a secondary thread?
I’d prefer something cross-platform.
I’m writing a library (C++) that exposes a Python API, and will be called interactively from a iPython, so all public functions need to return fairly quickly. This library also needs to run a simulation, and display a window(s). One way to solve this problem is to have the message loop on a background thread. I know this approach works on Windows, but from what I’ve read, causes problems on Mac.
I have read the post which says Cocoa apps need to poll from the main thread, and trying to figure out a way around it: Multithreading GLFW?
What if, on the main thread I installed a signal handler on the main thread, and raised a signal to the same process from a background thread? The signal handler should (I think) be invoked on the main thread, and this here could poll events on the main thread, and return back to listening for console input.
See this post on multithreading in GLFW and also see the GLFW documentation as each function description describes it’s thread safety.
The best approach is thus to keep GLFW event processing on the main thread and spawn work on other threads. Rendering can be moved to another thread using the context API. If desired you can use glfwWaitEvents on the main thread to block that thread until events are made available, and if you need to wake the main thread you can use glfwPostEmptyEvent to do so.
Can glfwPollEvents be called in a a signal handler?
On Windows, this is no problem because I can run a GetMessage/DispatchMessage on a background thread. But OSX needs a message loop on the main thread, I get it.
NSApp nextEventMatchingMask: NSAnyEventMask, and
[NSApp sendEvent: ev]; will hard fail on newer versions of OSX if called from other than the main thread.
So, what if these were called in a signal handler, say a SIGUSR1 handler?
The deal is my library (C++, Python interface) will be invoked interactively in a ipython session, this is by definition on the main thread. My lib needs to create a background window, run a simulation there which displays output in that window, all while still listening for user keyboard input.
So, what I was thinking I could have a ‘runAsync’ method in my program, this would create a window. The method would then install a handler for the SIGUSR1, spawn a background thread, and return. This puts the main thread back at listening for keyboard input.
The background thread would then continuously raise a SIGUSR1 signal, this would invoke the hander on the main thread, and this handler could then call glfwPollEvents and return.
This way, the main thread could both listen for keyboard user input, and process NSAppication events.
Have you considered running this as a separate process with interprocess communication? This would seem more natural given what you want.
Alternatively you could require the Python interface to call a pollevents function regularly for input which in turn calls glfwPollEvents. Doing this in a signal handler might be possible, but it sounds overly complicated.
Ultimately this sounds a complex problem which requires some domain knowledge and testing. So long as you call glfwPollEvents on the main thread at regular enough intervals to receive input when required you should be able to make this work.
Yes, have considered running as a separate process. However, this would entail writing a huge amount of code: I’d have to create a ‘proxy’ interface on one side, and a stub on the other, and write marshaling code for hundreds of different methods. It would take weeks to write all the marshaling code.
Then on top of that, every time a method changes, I’d have to change the marshaling code.
It does however look like ipython provides an input hook feature, where you can hook into the idle processing. They already provide input hooks for a variety of different toolkits, where the input hook manages an event loop.
What’s going the be the easiest if for my to write a glfw event loop, expose it as a python method, and hook it up to the ipython input hook.
I think this approach will work, starting to write some prototype code for it now.
That sounds the simplest solution - good luck!