exellian / rui

Rust framework for building modern ui
3 stars 0 forks source link

Event Loop + Async Runtime #28

Closed exellian closed 2 years ago

exellian commented 2 years ago

Choosing the right application lifecycle is somehow hard:

Situation Description:

Problem Description:

  1. The hard part is now to integrate the event waiting with an asynchronous runtime, because rust futures follow a polling approach. So let's say we have futures on a thread which needs to be executed. Then we don't want to suspend the current thread because we want the futures to be executed as fast as possible. So if we wait now for a new event we loose time when waiting or even worse don't execute the futures at all when no event happens. Especially on macos this is a problem, because macos doesn't let us poll events (checking for new events and not suspending the thread). Or at least it is very very hard and hacky to do.
  2. Another problem is the inter-dependency of occurring events. What I mean by that is that there are events that must be processed in a blocking way on the thread where the event occurred to not lead to undefined behaviour. One candidate for this type of event is the resize event. If a resize event happens, it must be handled on the same thread before another resize event can happen. Otherwise this would lead to rendering artefacts or surface recreation errors.

Problem resolution

  1. Try to hack our way through to event polling on macos and on all other platforms => https://github.com/exellian/rui/issues/28#issuecomment-1109153317
  2. None yet. Overthink async approach: In which case is it really useful to have an async context => #29
exellian commented 2 years ago

For Problem 1. I found a way to achieve polling on macos by trying out several things:

So the normal way of running a main loop is to call the run method on the NSApp instance:

[NSApp run]

This approach is an internal implementation and follows a waiting based approach. The way I found to create custom polling event loop is the following:

while (running) {
    NSEvent* event = [
        NSApp
        nextEventMatchingMask:NSEventMaskAny
        untilDate:nil // that was the trick
        inMode:NSDefaultRunLoopMode
        dequeue:YES
    ];
    if (event != NULL) {
        [NSApp sendEvent:event];
    }
    [NSApp updateWindows];
}

The main trick is to pass nil to the untilDate parameter of the call to nextEventMatchingMask method. This has the effect that the nextEventMatchingMask also returns when no event is in the queue and it returns NULL in this case. I tested the code example above and got 100% CPU usage and working window event handling which indicates that polling works. I can't verify that this is the way you should do it because apple lacks documentation and also doesn't provide open source of cocoa.

For a full version of the event loop we should look at the cocotron implementation of NSApplication and at the blog article demystifying-nsapplication