4 releases
0.1.3 | Nov 29, 2021 |
---|---|
0.1.2 | Nov 29, 2021 |
0.1.1 | Jan 11, 2021 |
0.1.0 | Jan 11, 2021 |
#413 in Procedural macros
20KB
276 lines
Winit-main
This is a winit
utility which abstracts away
winit's event-loop inversion of control.
Rationale
Winit necessarily hijacks the main thread due to platform constraints, creating a "don't call us, we'll call you" situation. Inversions of control have some undesirable properties, including:
- It's difficult to add inversion of control to a program after the fact, as it tends to fundamentally affect the program's architecture.
- For the above reason, it's difficult to write programs which are generic between inversions-of-control, or to modify a program from using one inversion-of-control framework to using a different framework.
- It's tricky to use several inversions of control simultaneously. For example, it would be difficult to combine tokio with winit without creating additional abstractions.
Solution
This library spawns your code on a second thread (a "simulated main thread"), hijacks the real main thread with winit's event loop, and provides your code handles to communicate with the main event loop. This allows you to write your program as you would any other program, treating winit's event loop as an iterator of events and a handle with which to create windows and ask about the system. When the simulated main thread exits, it triggers the event loop to exit, shutting down the process, just like if it were the real main thread.
Handling of Control Flow
Blockers
The simulated main thread receives winit Event
s through an EventReceiver
.
In these events, the user event type is a Blocker
. This is a concurrency
structure emitted by the main thread which blocks the event loop from
processing further winit events until the Blocker
is dropped. This is a way
to synchronize the event loop with the simulated main thread to some extent,
such as to synchronize the presenting of images.
Whenever the event loop encounters a RedrawRequested
event, it immediately
emits a Blocker
, and thus will not proceed until the simulated main thread
receives and drops that Blocker
.
ControlFlow
This library keeps the winit event loop in the ControlFlow::Wait
state.
Therefore, if you want to redraw a window in a loop, you should call
Window::request_redraw
after every draw.
Example
Without winit-main
:
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).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-main
(no proc macro):
use winit_main::reexports::{
event::{Event, WindowEvent},
window::WindowAttributes,
};
fn main() {
winit_main::run(|event_loop, events| {
let window = event_loop
.create_window(WindowAttributes::default())
.unwrap();
for event in events.iter() {
if matches!(
event,
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id()
) {
break;
}
}
});
}
With winit-main
(with proc macro):
use winit_main::{
reexports::{
event::{Event, WindowEvent},
window::WindowAttributes,
},
EventLoopHandle,
EventReceiver,
};
#[winit_main::main]
fn main(event_loop: EventLoopHandle, events: EventReceiver) {
let window = event_loop
.create_window(WindowAttributes::default())
.unwrap();
for event in events.iter() {
if matches!(
event,
Event::WindowEvent {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id()
) {
break;
}
}
}
Project Maturity
This project is young, barely tested, and probably poorly optimized. The main
form of test is the wgpu-example
package in this repository, which is the
triangle example from the wgpu
project, modified to utilize this abstraction.
Dependencies
~2.3–3.5MB
~77K SLoC