winit_block_on/lib.rs
1// SPDX-License-Identifier: BSL-1.0 OR Apache-2.0
2// Copyright John Nunley, 2023.
3// Distributed under the Boost Software License, Version 1.0 or the Apache
4// License, Version 2.0.
5// (See accompanying file LICENSE or copy at
6// https://www.boost.org/LICENSE_1_0.txt)
7
8//! A simple wrapper around `winit` that allows one to block on a future using the `winit` event loop.
9//!
10//! `winit` does not support `async` programming by default. This crate provides a small workaround
11//! that allows one to block on a future using the `winit` event loop.
12//!
13//! ## Examples
14//!
15//! ```no_run
16//! use winit::event::{Event, WindowEvent};
17//! use winit::event_loop::{ControlFlow, EventLoopBuilder};
18//! use winit::window::WindowBuilder;
19//! use winit_block_on::prelude::*;
20//!
21//! use std::future::pending;
22//! use std::time::Duration;
23//!
24//! // Create an event loop.
25//! let event_loop = EventLoopBuilder::new_block_on().build();
26//!
27//! // Create a window inside the event loop.
28//! let window = WindowBuilder::new().build(&event_loop).unwrap();
29//!
30//! // Create a proxy that can be used to send events to the event loop.
31//! let proxy = event_loop.create_proxy();
32//!
33//! // Block on the future indefinitely.
34//! event_loop.block_on(
35//! move |event, _, control_flow| {
36//! match event {
37//! Event::UserEvent(()) => control_flow.set_exit(),
38//! Event::WindowEvent {
39//! event: WindowEvent::CloseRequested,
40//! window_id
41//! } if window_id == window.id() => control_flow.set_exit(),
42//! _ => {}
43//! }
44//! },
45//! async move {
46//! // Wait for one second.
47//! async_io::Timer::after(Duration::from_secs(1)).await;
48//!
49//! // Tell the event loop to close.
50//! proxy.send_event(()).unwrap();
51//! }
52//! )
53//! ```
54//!
55//! This is a contrived example, since `control_flow.set_wait_deadline()` can do the same thing. See
56//! the `networking` example for a more complex and in-depth example of combining `async` with `winit`.
57//!
58//! ## Limitations
59//!
60//! In both cases, the user event `T` needs to be `Send` and `'static`. This is because the event loop
61//! proxy needs to be put into a `Waker`. In addition, if you are not using `run_return`, the future
62//! needs to be `'static`. Both of these limitations could be removed if `block_on` were integrated
63//! directly into `winit`.
64
65#![forbid(unsafe_code)]
66
67mod run_return;
68pub use run_return::*;
69
70use std::convert::Infallible;
71use std::future::Future;
72use std::sync::{Arc, Mutex};
73use std::task::{Context, Wake, Waker};
74
75use winit::event::Event;
76use winit::event_loop::{
77 ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget as Elwt,
78};
79
80/// Import all relevant traits for this crate.
81pub mod prelude {
82 pub use super::{EventLoopBuilderExt, EventLoopExt};
83}
84
85/// An extension trait for `EventLoop` that allows one to block on a future.
86pub trait EventLoopExt {
87 type User;
88
89 /// Block on the provided future indefinitely.
90 fn block_on<F, Fut>(self, handler: F, fut: Fut) -> !
91 where
92 F: FnMut(Event<'_, Self::User>, &Elwt<Signal<Self::User>>, &mut ControlFlow) + 'static,
93 Fut: Future<Output = Infallible> + 'static;
94}
95
96impl<T: Send + 'static> EventLoopExt for EventLoop<Signal<T>> {
97 type User = T;
98
99 fn block_on<F, Fut>(self, mut handler: F, fut: Fut) -> !
100 where
101 F: FnMut(Event<'_, Self::User>, &Elwt<Signal<Self::User>>, &mut ControlFlow) + 'static,
102 Fut: Future<Output = Infallible> + 'static,
103 {
104 // We need to pin the future on the heap, since the callback needs to be movable.
105 let mut fut = Box::pin(fut);
106 let mut ready = true;
107
108 // Create a waker that will wake up the event loop.
109 let waker = make_proxy_waker(&self);
110
111 self.run(move |event, target, control_flow| {
112 // If we got a wakeup signal, process it.
113 match event {
114 Event::UserEvent(Signal(Inner::Wakeup)) => {
115 // Make sure the future is ready to wake up.
116 ready = true;
117 }
118
119 Event::UserEvent(Signal(Inner::User(user))) => {
120 // Forward the user event to the inner callback.
121 let event = Event::UserEvent(user);
122 handler(event, target, control_flow);
123 }
124
125 Event::RedrawEventsCleared => {
126 // The handler may be interested in this event.
127 handler(Event::RedrawEventsCleared, target, control_flow);
128
129 // Since we are no longer blocking any events, we can process the future.
130 if ready {
131 ready = false;
132
133 // Eat the unused poll warning, it should never be ready anyways.
134 let _ = fut.as_mut().poll(&mut Context::from_waker(&waker));
135 }
136 }
137
138 event => {
139 // This is another type of event, so forward it to the inner callback.
140 let event: Event<T> =
141 event.map_nonuser_event().unwrap_or_else(|_| unreachable!());
142 handler(event, target, control_flow);
143 }
144 }
145 })
146 }
147}
148
149fn make_proxy_waker<T: Send + 'static>(evl: &EventLoop<Signal<T>>) -> Waker {
150 let proxy = evl.create_proxy();
151 Waker::from(Arc::new(ProxyWaker(Mutex::new(proxy))))
152}
153
154struct ProxyWaker<T: 'static>(Mutex<EventLoopProxy<Signal<T>>>);
155
156impl<T> Wake for ProxyWaker<T> {
157 fn wake(self: Arc<Self>) {
158 self.0
159 .lock()
160 .unwrap()
161 .send_event(Signal(Inner::Wakeup))
162 .ok();
163 }
164
165 fn wake_by_ref(self: &Arc<Self>) {
166 self.0
167 .lock()
168 .unwrap()
169 .send_event(Signal(Inner::Wakeup))
170 .ok();
171 }
172}
173
174/// An extension trait for `EventLoopBuilder` that allows one to construct an
175/// event loop that can block on the future.
176pub trait EventLoopBuilderExt {
177 /// Starts building a new async event loop.
178 fn new_block_on() -> Self;
179}
180
181impl<T> EventLoopBuilderExt for EventLoopBuilder<Signal<T>> {
182 fn new_block_on() -> Self {
183 Self::with_user_event()
184 }
185}
186
187/// The signal used to notify the event loop that it should wake up.
188pub struct Signal<T>(Inner<T>);
189
190impl<T> From<T> for Signal<T> {
191 fn from(t: T) -> Self {
192 Self(Inner::User(t))
193 }
194}
195
196enum Inner<T> {
197 User(T),
198 Wakeup,
199}