@stackotter I agree the MainRunLoopTickler
is a less than ideal solution. We are eagerly awaiting custom this work so that we can replace it with a custom main executor based off the Windows App SDK DispatcherQueue
I wanted to point out that this SomeMainActor.main() will not work for my use case (Android) where natively-compiled Swift code has to defer to its JVM overlord, Looper.main.
Taking over the main looper by running something like .main() (or RunLoop.run()) in Swift makes the Android system think the app has stopped responding.
I think generally weâre all on the same page with this but wanted to mention it explicitly.
I wanted to point out that this SomeMainActor.main() will not work for my use case (Android) where natively-compiled Swift code has to defer to its JVM overlord, Looper.main.
Taking over the main looper by running something like .main() (or RunLoop.run()) in Swift makes the Android system think the app has stopped responding.
The point of this work is that you'd be able to make an Android specific executor whose run()
method did Looper.loop()
, and whose stop()
method did Looper.getMainLooper().quitSafely()
. Something like that, at any rate. I'm not an Android developer so I'm guessing a little here, and there are some nuances here â e.g. you might want to stash a reference to the Looper
in the executor and use that instead of using getMainLooper()
, because then you could use your executor on threads other than the main thread (assuming that's an interesting thing to do).
Anyway, once you have that, this work would let you set it as your main executor, and Swift Concurrency would work as intended.
It's worth noting that I'm currently working on a Win32 native executor that sits on top of the work we're talking about here. You could easily build the DispatcherQueue
one on top of the Win32 one by calling ContentPreTranslateMessage(&msg)
from the delegate on the Win32 native executor and by having your DispatcherQueue
executor call DispatcherQueueController::CreateOnCurrentThread()
and DispatcherQueueController::ShutdownQueue()
before and after calling the Win32 executor's run()
function.
That would avoid having to worry about any of the other details of making an executor work.
I'm not sure we'd want the DispatcherQueue
implementation to be part of Swift Concurrency itself, since the Windows App SDK is only one of a number of things you could use to build a Windows application, and using it from Concurrency directly would cause us to always load the underlying library code, even when we weren't using it.
I donât quite understand the suggestion. I donât think itâs possible for Swift code to call Looper.run() but still continue to run Swift code. And vice-versa. Thatâs the core issue Iâm getting at. There are two systems trying to âbeâ the main looper.
As I say, I'm not an Android developer so I'm not entirely familiar with the environment, but from what I can see in the documentation, you'd have your Swift executor process enqueued jobs from the MessageQueue
's idle handler. You'd need to also have some kind of Message
to wake the looper up (the handler for which could do nothing â you'd likely just want to run the enqueued jobs from the idle handler), so that enqueuing to an idle looper from another thread worked correctly.
TL/DR: The Swift code doesn't need to carry on running Swift code when it calls Looper.run()
. At that point, the Android Looper
is indeed the main event loop.
In my various Android+Swift prototypes, my runMainLoop implementation (different api but similar to SomeMainActor.main) just does nothing and falls through so that the swift code returns control to java. I believe that should be possible with the pitched API as long as main isnât required to return Never.
Based on the last few posts, itâs clear to me that I donât know what is being proposed instead. âWhoâ is draining the swift main job queue? And when is it scheduled, how is priority decided, etc.?
All the suggestions of ârunMainLoopâ etc. doing nothing in Swift and returning control to someone else are all fine, provided someone is actually draining the queue which is filled by things like @MainActor functions, etc.
Up until this point I havenât seen any concrete suggestion of how to do that, but I may have missed it.
If the suggestion is âdonât use the real MainActor, use some other actor and pretend itâs the main actorâ that wonât work for my use case: it has to be the identical main thread / queue / actor as the JVM (or the web browser in the case of Wasm).
My suggestion was to set an idle handler on your Android MessageQueue
that calls into the Swift executor to process the main job queue. Then in your executor, when a new job is added to the queue, send a Message
to wake the Looper
up; the handler for that Message
can be empty â you just need to make the Looper
wake up, so that it triggers the idle handler and gives the Swift executor chance to process jobs in the main queue.
Then you'd have your Swift executor's run
method call Looper.run()
. You wouldn't be trying to return from your Swift code to some outer event loop â instead you'd be running the Android event loop within the Swift executor's run
method.
Something like that, anyway. As I said, I'm not an Android developer, so this is all a bit speculative and I've based it on a quick scan of the Android API documentation.