How the Swift concurrency interacts with thread requirements for graphics libraries?

So, I made a little project to test using swift with raylib some time ago, it sure is very naive and all, but was quite neat to just learn how to put the two together

The thing is, how is said in lines 250 to 256, that if I don't have that call to the Board actor's method, GLFW (being used by raylib) complains about not running in the current context when swapping buffers (this occurs also even if there's no async call in the drawing loop)

And I'm just confused as to how should I properly make this program work, without hacks, and specifically why (including why does that hacky call make the program work, also thinking back why those input/draw calls on the actor seem to work fine as technically they're not in the same context?)

Other things I've tried is changing this from top level code to an entry point struct with @MainActor, and it doesn't seem to change the behaviour

I've also tried wrapping the loop and window setup functions in DispatchQueue.main.async, and the behaviour is that if I just wrap the window creation functions, the program runs without error (at least from my testing, but I'm still not sure this is the proper way to do it), though also trying to wrap the drawing loop, it doesn't run at all

I've tried searching around but at least couldn't find specifically about the interaction between Swift programs with concurrency and external libraries that care about which threads they're run on

Last tested this program in Swift 6.0.1

GLFW requires you to call most functions from the main thread. This is fine (use `@MainActor).

But OpenGL maintains a current context per-thread, and you can use glfwMakeContextCurrent (read the docs! there are caveats!) to set the current context.

Since Swift concurrency doesn't guarantee what thread anything runs on, the tl;dr is "poorly"

1 Like

You can use a custom executor to tie an actor to a particular thread, but I haven't tried it myself yet. swift-evolution/proposals/0392-custom-actor-executors.md at main · swiftlang/swift-evolution · GitHub

3 Likes

Yeah this is possible to pull off, but is quite hard. I did it by using a thread + runloop and a few unsafe opt-outs.

(“It” being an actor backed by a single dedicated thread.)

The thing is I'm not sure about either of these options

As I've already tried having either the render part, or the program entry point and loop be a @MainActor func, and it didn't really seem to change the behaviour

And using glfwMakeContextCurrent seems to be not an option when using raylib specifically as it encapsulates the render context from the user (even importing glfw and using it directly seems to not really be able to get the context)

Also as I've said, apparently putting a DispatchQueue.main.async around just the window initialisation calls seems to work (putting it on the render loop seems to make it not run), but I don't think that's the right way, as it still would not make sure the render loop (more specifically the 'EndDrawing') is running on main, so would still be a flimsy hack?