winit-modular: proxy winit event loops which can be run at the same time, on separate threads

Provides an API very similar to winit except the EventLoop type can be created on multiple threads, multiple event loops can exist simultaneously, you can poll for events or receive them asynchronously, and more.

Notice: This library is still very early in development, and certain features are not fully tested. If you encounter any issues, please submit an issue or PR on github.

The problem

winit is the de-facto way to create windows and listen to system events in Rust. However the core struct EventLoop, which allows you to create windows and listen to events, has some annoying restrictions:

which imply more restrictions:

There is also the issue of inversion of control which winit-main explains and attempts to solve.

The solution

This crate fixes winit's lack of modularity, sort of. It provides EventLoops which have a similar api to winit::event_loop::EventLoop, but:

This works as these EventLoops are actually proxies, which forward their calls and recieve events from the main event loop using asynchronous channels and atomics.

Drawbacks

This doesn't completely alleviate winit's issues and is not always drop-in replacement.

Most importantly, you must call winit_modular::run exactly once in your application, on the main thread, before using the event loops in this crate. If you don't you will get a panic message explaining this. as winit_modular::run hijacks the main thread, it provides a callback to run the rest of your code on the background thread.

Also the performance penalty from using multiple threads and sending messages across channels. The proxy event loops must communicate with the actual winit event loop across thread bounds, for every operation or intercepted event. That means, often once every frame refresh. Fortunately, modern hardware is generally fast enough and threading is good enough that even then it's a minor performance penalty. But on embedded systems, or if you are spawning a lot of proxy event loops simultaneously, it could be an issue.

Example (originally from winit-main)

Without winit_modular:

```rust use winit::{ event::{Event, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, };

fn main() { let eventloop = EventLoop::new(); let window = WindowBuilder::new().build(&eventloop).unwrap();

event_loop.run(move |event, _, control_flow| {
    *control_flow = ControlFlow::Wait;

    if matches!(
        event,
        Event::WindowEvent {
            event: WindowEvent::CloseRequested,
            window_id,
        } if window_id == window.id()
    ) {
        *control_flow = ControlFlow::Exit;
    }
});

} ```

With winit-modular:

```rust use winitmodular::{ event::{Event, WindowEvent}, eventloop::{ControlFlow, EventLoop} }; use pollster::block_on;

fn main() { winitmodular::run(|| blockon(async { let event_loop = EventLoop::new().await;

    let window = event_loop
        .create_window(|builder| builder)
        .await
        .unwrap();

    event_loop.run_async(|event, control_flow| {
        if matches!(
            event,
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                window_id,
            } if window_id == window.id()
        ) {
            *control_flow = ControlFlow::ExitApp;
        }
    }).await;
}));

} ```