frenderer/
events.rs

1//! The winit feature enables two useful helpers.  First is an
2//! extension trait [`FrendererEvents`] that simplifies the connection
3//! between winit's event loop stages and a game rendering/simulation
4//! lifecycle.  Second is the [`Driver`] struct that manages winit's
5//! event loop and initializes both a window and the graphics context
6//! once the proper winit events have arrived.
7
8use std::sync::Arc;
9
10/// Phase in the game event loop
11pub enum EventPhase {
12    /// The game should simulate time forward by the given number of steps and then render.  Typically the caller of [`FrendererEvents::handle_event`] should respond to this by calling `render` on the [`crate::frenderer::Renderer`].
13    Run(usize),
14    /// The game should terminate as quickly as possible and close the window.
15    Quit,
16    /// There's nothing in particular the game should do right now.
17    Wait,
18}
19
20/// This extension trait is used under the `winit` feature to simplify event-loop handling.
21pub trait FrendererEvents<T> {
22    /// Call `handle_event` on your [`crate::frenderer::Renderer`]
23    /// with a given [`crate::clock::Clock`] to let Frenderer
24    /// figure out "the right thing to do" for the current `winit`
25    /// event.  See [`crate::clock::Clock`] for details on the timestep computation.
26    fn handle_event(
27        &mut self,
28        clock: &mut crate::clock::Clock,
29        window: &Arc<winit::window::Window>,
30        evt: &winit::event::Event<T>,
31        target: &winit::event_loop::EventLoopWindowTarget<T>,
32        input: &mut crate::input::Input,
33    ) -> EventPhase;
34}
35impl<T> FrendererEvents<T> for crate::Renderer {
36    fn handle_event(
37        &mut self,
38        clock: &mut crate::clock::Clock,
39        window: &Arc<winit::window::Window>,
40        evt: &winit::event::Event<T>,
41        _target: &winit::event_loop::EventLoopWindowTarget<T>,
42        input: &mut crate::input::Input,
43    ) -> EventPhase {
44        use winit::event::{Event, WindowEvent};
45        match evt {
46            Event::Resumed if self.surface().is_none() => {
47                self.create_surface(Arc::clone(window));
48                EventPhase::Wait
49            }
50            Event::WindowEvent {
51                event: WindowEvent::CloseRequested,
52                ..
53            } => EventPhase::Quit,
54            winit::event::Event::WindowEvent {
55                event: winit::event::WindowEvent::Resized(size),
56                ..
57            } => {
58                // For some reason on web this causes repeated increases in size.
59                if !self.gpu.is_web() {
60                    self.resize_surface(size.width, size.height);
61                }
62                window.request_redraw();
63                EventPhase::Wait
64            }
65            Event::WindowEvent {
66                event: WindowEvent::RedrawRequested,
67                ..
68            } => {
69                let steps = clock.tick();
70                window.request_redraw();
71                EventPhase::Run(steps)
72            }
73            event => {
74                input.process_input_event(event);
75                EventPhase::Wait
76            }
77        }
78    }
79}
80impl<T> FrendererEvents<T> for crate::Immediate {
81    fn handle_event(
82        &mut self,
83        clock: &mut crate::clock::Clock,
84        window: &Arc<winit::window::Window>,
85        evt: &winit::event::Event<T>,
86        target: &winit::event_loop::EventLoopWindowTarget<T>,
87        input: &mut crate::input::Input,
88    ) -> EventPhase {
89        self.renderer
90            .handle_event(clock, window, evt, target, input)
91    }
92}
93/// Driver takes ownership of winit's event loop and creates a window and graphics context when possible.
94pub struct Driver {
95    builder: winit::window::WindowBuilder,
96    render_size: Option<(u32, u32)>,
97}
98#[cfg(all(target_arch = "wasm32", feature = "winit"))]
99pub mod web_error {
100    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
101    pub enum RuntimeError {
102        NoCanvas,
103        NoDocument,
104        NoBody,
105    }
106    impl std::fmt::Display for RuntimeError {
107        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108            <Self as std::fmt::Debug>::fmt(self, f)
109        }
110    }
111    impl std::error::Error for RuntimeError {}
112}
113
114struct NoopWaker();
115impl std::task::Wake for NoopWaker {
116    fn wake(self: std::sync::Arc<Self>) {
117        //nop
118    }
119}
120
121impl Driver {
122    /// Create a [`Driver`] with the given window builder and render target size (if absent, will use the window's inner size instead).
123    pub fn new(builder: winit::window::WindowBuilder, render_size: Option<(u32, u32)>) -> Self {
124        Self {
125            builder,
126            render_size,
127        }
128    }
129    /// Kick off the event loop. Once the driver receives the
130    /// [`winit::event::Event::Resumed`] event, it will initialize
131    /// Frenderer and call `init_cb` with the window and renderer.
132    /// This callback may return an application state object or
133    /// userdata which will be passed as the final argument to
134    /// `handler`, which will be called for every winit event /after/
135    /// `init_cb` has been called.  If you don't want `run_event_loop`
136    /// to own your application-specific data, you could instead store
137    /// such data yourself in internally mutable types such as
138    /// [`std::cell::OnceCell`] and evaluate `init_cb` for its side
139    /// effects.
140    ///
141    /// Example:
142    /// ```
143    ///    drv.run_event_loop::<(), _>(
144    ///      move |window, mut frend| {
145    ///        let mut camera = Camera2D {
146    ///          screen_pos: [0.0, 0.0],
147    ///          screen_size: [1024.0, 768.0],
148    ///        };
149    ///        init_data(&mut frend, &mut camera);
150    ///        (window, camera, frend)
151    ///      },
152    ///      move |event, target, (window, camera, frend)| {
153    ///        // handle the winit event here, and maybe do some rendering!
154    ///      });
155    /// ```
156    pub fn run_event_loop<T: 'static, U: 'static>(
157        self,
158        init_cb: impl FnOnce(std::sync::Arc<winit::window::Window>, crate::Renderer) -> U + 'static,
159        mut handler: impl FnMut(winit::event::Event<T>, &winit::event_loop::EventLoopWindowTarget<T>, &mut U)
160            + 'static,
161    ) -> Result<(), Box<dyn std::error::Error>> {
162        enum DriverState<U: 'static> {
163            WaitingForResume(winit::window::WindowBuilder),
164            PollingFuture(
165                Arc<winit::window::Window>,
166                #[allow(clippy::type_complexity)]
167                std::pin::Pin<
168                    Box<
169                        dyn std::future::Future<
170                            Output = Result<crate::Renderer, Box<dyn std::error::Error>>,
171                        >,
172                    >,
173                >,
174            ),
175            Running(U),
176            // This is just used as a temporary value
177            InsideLoop,
178        }
179        use winit::event_loop::EventLoop;
180        let Self {
181            builder,
182            render_size,
183        } = self;
184        prepare_logging()?;
185        let event_loop: EventLoop<T> =
186            winit::event_loop::EventLoopBuilder::with_user_event().build()?;
187        let instance = Arc::new(wgpu::Instance::default());
188        let waker = Arc::new(NoopWaker()).into();
189        let mut init_cb = Some(init_cb);
190        let driver_state = std::cell::Cell::new(DriverState::WaitingForResume(builder));
191        let cb = move |event, target: &winit::event_loop::EventLoopWindowTarget<_>| {
192            target.set_control_flow(winit::event_loop::ControlFlow::Wait);
193            driver_state.set(match driver_state.replace(DriverState::InsideLoop) {
194                DriverState::WaitingForResume(builder) => {
195                    if let winit::event::Event::Resumed = event {
196                        let window = Arc::new(builder.build(target).unwrap());
197                        prepare_window(&window);
198                        let surface = instance.create_surface(Arc::clone(&window)).unwrap();
199                        let wsz = window.inner_size();
200                        let sz = render_size.unwrap_or((wsz.width, wsz.height));
201                        let future = Box::pin(crate::Renderer::with_surface(
202                            sz.0,
203                            sz.1,
204                            wsz.width,
205                            wsz.height,
206                            Arc::clone(&instance),
207                            Some(surface),
208                        ));
209                        DriverState::PollingFuture(window, future)
210                    } else {
211                        DriverState::WaitingForResume(builder)
212                    }
213                }
214                DriverState::PollingFuture(window, mut future) => {
215                    let mut cx = std::task::Context::from_waker(&waker);
216                    if let std::task::Poll::Ready(frend) = future.as_mut().poll(&mut cx) {
217                        let frenderer = frend.unwrap();
218                        let userdata = init_cb.take().unwrap()(Arc::clone(&window), frenderer);
219                        DriverState::Running(userdata)
220                    } else {
221                        // schedule again
222                        target.set_control_flow(winit::event_loop::ControlFlow::Poll);
223                        DriverState::PollingFuture(window, future)
224                    }
225                }
226                DriverState::Running(mut userdata) => {
227                    handler(event, target, &mut userdata);
228                    DriverState::Running(userdata)
229                }
230                DriverState::InsideLoop => {
231                    panic!("driver state loop unexpectedly reentrant");
232                }
233            });
234        };
235        #[cfg(not(target_arch = "wasm32"))]
236        {
237            Ok(event_loop.run(cb)?)
238        }
239        #[cfg(target_arch = "wasm32")]
240        {
241            use winit::platform::web::EventLoopExtWebSys;
242            Ok(event_loop.spawn(cb))
243        }
244    }
245}
246
247/// If you don't use [`Driver`], it may still be convenient to call
248/// `prepare_window` to set up a window in a cross-platform way
249/// (e.g. on web, it will add the window's canvas to the HTML
250/// document).
251#[allow(unused_variables)]
252pub fn prepare_window(window: &winit::window::Window) {
253    #[cfg(target_arch = "wasm32")]
254    {
255        use self::web_error::RuntimeError;
256        use crate::wgpu::web_sys;
257        use winit::platform::web::WindowExtWebSys;
258        let doc = web_sys::window()
259            .ok_or(RuntimeError::NoBody)
260            .unwrap()
261            .document()
262            .unwrap();
263        let canvas = window.canvas().ok_or(RuntimeError::NoCanvas).unwrap();
264        doc.body()
265            .ok_or(RuntimeError::NoBody)
266            .unwrap()
267            .append_child(&canvas)
268            .unwrap();
269    }
270}
271
272/// If you don't use [`Driver`], it may still be convenient to call
273/// `prepare_logging` to set up `env_logger` or `console_log`
274/// appropriately for the platform.
275pub fn prepare_logging() -> Result<(), Box<dyn std::error::Error>> {
276    #[cfg(not(target_arch = "wasm32"))]
277    {
278        env_logger::init();
279        Ok(())
280    }
281    #[cfg(target_arch = "wasm32")]
282    {
283        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
284        console_log::init_with_level(log::Level::Warn)?;
285        Ok(())
286    }
287}