1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
//! This is a [`winit`](https://lib.rs/crates/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`:
//!
//! ```rust,no_run
//! 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):
//!
//! ```rust,no_run
//! 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):
//!
//! ```rust,compile_fail
//! 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;
//! }
//! }
//! }
//! ```
use std::{
sync::mpsc,
thread,
time::Duration,
iter,
panic::{
catch_unwind,
AssertUnwindSafe,
},
};
use winit::{
event_loop::{
EventLoop,
EventLoopProxy,
ControlFlow,
},
event::Event,
monitor::MonitorHandle,
window::{
WindowAttributes,
Window,
},
error::OsError,
};
use crate::request::{
Request,
RequestMessage,
RequestCallback,
GetAvailableMonitors,
GetPrimaryMonitor,
CreateWindow,
};
#[cfg(feature = "proc")]
pub use winit_main_proc::main;
mod request;
/// Re-exports of `winit` modules.
///
/// Re-exports all `winit` modules except `winit::event_loop`.
pub mod reexports {
// re-export everthing except `event_loop`
pub use winit::{
dpi,
error,
event,
monitor,
platform,
window,
};
}
/// Message sent from the simulated main thread to the event loop.
enum Message {
/// Request for some function to be evaluated in the context of the event
/// loop and the response sent back to the sender. Sent by
/// `EventLoopHandle`.
Request(RequestMessage),
/// Request for the event loop, and therefore the entire process, to exit.
/// Sent when the simulated main thread's user function exits.
Exit,
/// Unblock the event loop from its currently blocked state. Sent to the
/// event loop once, no more and no less, after and only after the event
/// loop sends out a `Blocked` user event.
Unblock,
}
/// Handle for sending requests to the main event loop and receiving responses.
#[derive(Clone)]
pub struct EventLoopHandle {
// use this to wake the event loop up and trigger it to process messages
wake_sender: EventLoopProxy<()>,
// use this to actually send the message
msg_send: mpsc::Sender<Message>,
}
fn sleep_forever() -> ! {
loop {
thread::sleep(Duration::new(u64::MAX, 1_000_000_000 - 1));
}
}
impl EventLoopHandle {
/// Send a request, wait for a response.
fn request_wait<R>(&self, request: R) -> R::Response
where
R: Request,
RequestMessage: From<RequestCallback<R>>,
{
// pair the request with a channel for the response to return on
let (send_response, recv_response) = mpsc::channel();
let request = RequestMessage::from(RequestCallback {
request,
callback: send_response,
});
// send the request
let _ = self.msg_send.send(Message::Request(request));
// trigger the event loop to wake up and process the request
let _ = self.wake_sender.send_event(());
// wait for the response
match recv_response.recv() {
Ok(response) => response,
Err(mpsc::RecvError) => sleep_forever(),
}
}
/// The list of all monitors available on the system.
///
/// Equivalent to
/// `winit::event_loop::EventLoopWindowTarget::available_monitors`.
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
self.request_wait(GetAvailableMonitors)
}
/// The primary monitor of the system.
///
/// Equivalent to
/// `winit::event_loop::EventLoopWindowTarget::primary_monitor`.
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
self.request_wait(GetPrimaryMonitor)
}
/// Attempt to create a new window.
///
/// Equivalent to `winit::window::WindowBuilder::build`.
pub fn create_window(&self, attributes: WindowAttributes) -> Result<Window, OsError> {
self.request_wait(CreateWindow(attributes))
}
}
/// Concurrency structure, emitted as a user event immediately after certain
/// other events are emitted, which blocks the event loop until this `Blocker`
/// is dropped.
pub struct Blocker(mpsc::Sender<Message>);
impl Drop for Blocker {
fn drop(&mut self) {
let _ = self.0.send(Message::Unblock);
}
}
impl Blocker {
/// Unblock the event loop. This is only to facilitate readability, since
/// `Blocker` unblocks the event loop when dropped.
pub fn unblock(self) {
drop(self)
}
}
/// Handle for receiving events from the main event loop.
///
/// Unlike a raw `std::sync::mpsc::Receiver`, this never returns error on
/// disconnection, because disconnection can only occur for a brief moment
/// between the main event loop beginning to shut down, and the process as a
/// whole exiting. Therefore, when this receives a disconnection error from
/// the underlying receiver, it enters an infinite sleep cycle as it waits for
/// the OS to kill the process.
pub struct EventReceiver(mpsc::Receiver<Event<'static, Blocker>>);
impl EventReceiver {
/// Receive an event, blocking until one is available.
pub fn recv(&self) -> Event<'static, Blocker> {
match self.0.recv() {
Ok(event) => event,
Err(mpsc::RecvError) => sleep_forever(),
}
}
/// Attempt to receive an event, blocking until one is available, or the
/// `timeout` duration has passed.
pub fn recv_timeout(&self, timeout: Duration) -> Option<Event<'static, Blocker>> {
match self.0.recv_timeout(timeout) {
Ok(event) => Some(event),
Err(mpsc::RecvTimeoutError::Timeout) => None,
Err(mpsc::RecvTimeoutError::Disconnected) => sleep_forever(),
}
}
/// Try to receive an event immediately, never blocking.
pub fn try_recv(&self) -> Option<Event<'static, Blocker>> {
match self.0.try_recv() {
Ok(event) => Some(event),
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => sleep_forever(),
}
}
/// Iterator form of `self.recv()`. Blocking iterator that never ends.
pub fn iter<'a>(&'a self) -> impl Iterator<Item=Event<'static, Blocker>> + 'a {
iter::from_fn(move || Some(self.recv()))
}
/// Iterator form of `self.try_recv()`. Non-blocking iterator that drains
/// the events currently in the queue.
pub fn try_iter<'a>(&'a self) -> impl Iterator<Item=Event<'static, Blocker>> + 'a {
iter::from_fn(move || self.try_recv())
}
}
/// Hijack the main thread with a winit event loop, and spawn a new thread with
/// callbacks to communicate with the main thread.
///
/// When the new thread, the "simulated main thread" exits, the event loop will
/// also exit loop. This is this is the primary abstraction of this crate, as
/// it abstracts away `winit`'s inversion of control, and allows `winit` to be
/// used more like any other library.
pub fn run<F>(f: F) -> !
where
F: FnOnce(EventLoopHandle, EventReceiver) + Send + 'static
{
// create event loop
let event_loop = EventLoop::with_user_event();
// create queues
let (event_send, event_recv) = mpsc::channel();
let (msg_send, msg_recv) = mpsc::channel();
let msg_send_1 = msg_send;
let msg_send_2 = msg_send_1.clone();
let msg_send_3 = msg_send_1.clone();
let wake_sender_1 = event_loop.create_proxy();
let wake_sender_2 = event_loop.create_proxy();
// spawn simulated main thread
thread::spawn(move || {
let handle = EventLoopHandle {
wake_sender: wake_sender_1,
msg_send: msg_send_1,
};
let receiver = EventReceiver(event_recv);
// run the user code
let _ = catch_unwind(AssertUnwindSafe(move || f(handle, receiver)));
// send the exit message to the event loop
let _ = msg_send_2.send(Message::Exit);
// wake up the event loop
let _ = wake_sender_2.send_event(());
});
// enter event loop
event_loop.run(move |event, window_target, control_flow| {
*control_flow = ControlFlow::Wait;
let event = match event.to_static() {
Some(event) => event,
None => return, // TODO: what if user wants the static event?
};
match event.map_nonuser_event() {
Ok(nonuser_event) => {
// send out event
let triggers_block = matches!(
&nonuser_event,
&Event::RedrawRequested(_)
);
let _ = event_send.send(nonuser_event);
if triggers_block {
// maybe send out a blocker, then block on it
let blocker = Blocker(msg_send_3.clone());
let _ = event_send.send(Event::UserEvent(blocker));
// we must still process messages while blocked blocked, or
// it would likely cause deadlock
'block: for msg in msg_recv.iter() {
match msg {
Message::Request(request) => {
request.run_respond(window_target);
}
Message::Unblock => {
break 'block;
},
Message::Exit => {
*control_flow = ControlFlow::Exit;
}
};
}
}
}
Err(Event::UserEvent(())) => {
// process messages
// the user event is sent to wake us up and trigger us to
// process messages after a message is sent
for msg in msg_recv.try_iter() {
match msg {
Message::Request(request) => {
request.run_respond(window_target);
}
Message::Unblock => unreachable!("not blocked"),
Message::Exit => {
*control_flow = ControlFlow::Exit;
}
};
}
}
Err(_) => unreachable!(),
};
});
}