Related to #1619 and #1779
It's unfortunate to have the autorelease pool managed by our examples. Need to investigate if we can completely automate it.
cc @grovesNL @scoopr @JohnColanduoni
If you really want to handle it within gfx, then I suppose the choices are
Instance creation. But I don't think there are other good natural "blocks" of code to surround autoreleasepool, and you would end up with a heuristic like "drain on present", which might get ugly and interact badly with other code that handles autorelease pools. Oh and it wouldn't help with multiple threads. At all.When I started writing this, I was leaning on 1. with some hesitance, but going through the downsides while writing, 2. seems to be the safe bet, it's always _correct_, and the downsides shouldn't be that bad.
I guess it would be nice to have a helper, like fn with_autorelease_pool<F:FnOnce>(func:F), or perhaps a struct with impl Drop. I suppose it could even check a thread boolean if we had already created pool and not do additional ones, but not sure if it is worth the cost. It could also be feature-flagged, so if I'd rather want to handle it myself, the gfx side autorelease pools would just vanish...
It's a bit late, I'm rambling :P
Thanks for the great summary @scoopr!
As mentioned on Gitter the performance of choice 2 seems like it could be fine, based on some performance tests (like this one). How did you know that libSDL does this, just by glancing through the source code?
I've got some rich insights from our graphics team (@jrmuizel and @mstange):
winit, and it polls events from Cocoa instead of registering a hander in the event loopThus, we might be seeing a lower level solution here, potentially. Some preliminary ideas:
run_forever, assuming it registers in Cocoa main loop. Make a timer to run 60fps and trigger an event for us to render.winit to see if there is anything else we can doAlso cc @tomaka @francesca64
On macOS, the implementation of run_forever is almost completely identical to poll_events, so option A wouldn't help. We basically try to force Cocoa to conform to the expectations of winit's design (calling it from our event loop rather than it calling us from its event loop), which has unsurprisingly led to difficult problems.
@willglynn knows a lot more about Cocoa than I do, so can hopefully offer more concrete advice on how to proceed.
I don't think winit can help with its current design. Right now, neither winit nor MacOS are underneath gfx in the call stack. If you want an autorelease pool, you're on your own.
My proposed solution to event loop problems in tomaka/winit#237 involves coroutines: user code runs on the usual stack, MacOS event loop runs on a different stack, and they cooperatively yield into each other. This addresses the mismatch where both APIs expect to call into the other, but I had not considered how this would interact with autorelease pools. (The only Objective-C objects winit needs to juggle are incoming events, and manual retain+release is sufficient there.) Autorelease pools are implemented using a thread-local stack of pointers which _normally_ follows the call stack, but… winit would be switching the call stack. I _think_ this solution means that autorelease pools would be unavailable for user code on the UI thread, since each poll_events() or run_forever() call is actually a return into MacOS on a separate stack.
I've come to believe that winit needs a more drastic shift, where the user must surrender their thread and operate solely in callbacks. I don't know if I've convinced anyone else, and I'm not currently pursuing that design, but such a change would put MacOS at the bottom of the stack, then winit, then you. This arrangement would eliminate the need for coroutines on MacOS, solve similar problems on basically every other winit platform, and have the side effect of making autorelease pools work automatically.
Then you are only left with the problem when you are not using winit at all because you didn't need a window for your bitcoin mining compute jobs ;)
I believe it has been perfectly okay to run the cocoa runloop on your own terms, there is just more stuff to take care of (like autorelease pools), and I guess thats how many of the wrapper libs like sdl/glfw etc. work. But that is actually not really possible on iOS (and android as well), and only way to run code is through platform provided hooks, there might be workarounds with separate threads.
I suppose winit could provide an optional run_forever(event_callback) and have it send Window::Refresh events with DisplayLink. Examples etc. could be changed to that model, but the "traditional" desktop model would still be available.
@scoopr As you note, mobile platforms don't fit the "you call the runloop" model. Web doesn't fit either – and in fact web requires both winit and the user's code to return entirely to the browser before receiving more events. Resizing windows on both MacOS and Windows causes the platform to enter an internal runloop from which winit cannot return, but from which winit could still deliver events.
"The runloop calls you" works everywhere.
Most helpful comment
I don't think
winitcan help with its current design. Right now, neitherwinitnor MacOS are underneathgfxin the call stack. If you want an autorelease pool, you're on your own.My proposed solution to event loop problems in tomaka/winit#237 involves coroutines: user code runs on the usual stack, MacOS event loop runs on a different stack, and they cooperatively yield into each other. This addresses the mismatch where both APIs expect to call into the other, but I had not considered how this would interact with autorelease pools. (The only Objective-C objects
winitneeds to juggle are incoming events, and manualretain+releaseis sufficient there.) Autorelease pools are implemented using a thread-local stack of pointers which _normally_ follows the call stack, but…Âwinitwould be switching the call stack. I _think_ this solution means that autorelease pools would be unavailable for user code on the UI thread, since eachpoll_events()orrun_forever()call is actually areturninto MacOS on a separate stack.I've come to believe that
winitneeds a more drastic shift, where the user must surrender their thread and operate solely in callbacks. I don't know if I've convinced anyone else, and I'm not currently pursuing that design, but such a change would put MacOS at the bottom of the stack, thenwinit, then you. This arrangement would eliminate the need for coroutines on MacOS, solve similar problems on basically every otherwinitplatform, and have the side effect of making autorelease pools work automatically.