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
//! The winit feature enables two useful helpers.  First is an
//! extension trait [`FrendererEvents`] that simplifies the connection
//! between winit's event loop stages and a game rendering/simulation
//! lifecycle.  Second is the [`Driver`] struct that manages winit's
//! event loop and initializes both a window and the graphics context
//! once the proper winit events have arrived.

use std::sync::Arc;

/// Phase in the game event loop
pub enum EventPhase {
    /// 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`].
    Run(usize),
    /// The game should terminate as quickly as possible and close the window.
    Quit,
    /// There's nothing in particular the game should do right now.
    Wait,
}

/// This extension trait is used under the `winit` feature to simplify event-loop handling.
pub trait FrendererEvents<T> {
    /// Call `handle_event` on your [`crate::frenderer::Renderer`]
    /// with a given [`crate::clock::Clock`] to let Frenderer
    /// figure out "the right thing to do" for the current `winit`
    /// event.  See [`crate::clock::Clock`] for details on the timestep computation.
    fn handle_event(
        &mut self,
        clock: &mut crate::clock::Clock,
        window: &Arc<winit::window::Window>,
        evt: &winit::event::Event<T>,
        target: &winit::event_loop::EventLoopWindowTarget<T>,
        input: &mut crate::input::Input,
    ) -> EventPhase;
}
impl<T> FrendererEvents<T> for crate::Renderer {
    fn handle_event(
        &mut self,
        clock: &mut crate::clock::Clock,
        window: &Arc<winit::window::Window>,
        evt: &winit::event::Event<T>,
        _target: &winit::event_loop::EventLoopWindowTarget<T>,
        input: &mut crate::input::Input,
    ) -> EventPhase {
        use winit::event::{Event, WindowEvent};
        match evt {
            Event::Resumed if self.surface().is_none() => {
                self.create_surface(Arc::clone(window));
                EventPhase::Wait
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => EventPhase::Quit,
            winit::event::Event::WindowEvent {
                event: winit::event::WindowEvent::Resized(size),
                ..
            } => {
                // For some reason on web this causes repeated increases in size.
                if !self.gpu.is_web() {
                    self.resize_surface(size.width, size.height);
                }
                window.request_redraw();
                EventPhase::Wait
            }
            Event::WindowEvent {
                event: WindowEvent::RedrawRequested,
                ..
            } => {
                let steps = clock.tick();
                window.request_redraw();
                EventPhase::Run(steps)
            }
            event => {
                input.process_input_event(event);
                EventPhase::Wait
            }
        }
    }
}
impl<T> FrendererEvents<T> for crate::Immediate {
    fn handle_event(
        &mut self,
        clock: &mut crate::clock::Clock,
        window: &Arc<winit::window::Window>,
        evt: &winit::event::Event<T>,
        target: &winit::event_loop::EventLoopWindowTarget<T>,
        input: &mut crate::input::Input,
    ) -> EventPhase {
        self.renderer
            .handle_event(clock, window, evt, target, input)
    }
}
/// Driver takes ownership of winit's event loop and creates a window and graphics context when possible.
pub struct Driver {
    builder: winit::window::WindowBuilder,
    render_size: Option<(u32, u32)>,
}
#[cfg(all(target_arch = "wasm32", feature = "winit"))]
pub mod web_error {
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum RuntimeError {
        NoCanvas,
        NoDocument,
        NoBody,
    }
    impl std::fmt::Display for RuntimeError {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            <Self as std::fmt::Debug>::fmt(self, f)
        }
    }
    impl std::error::Error for RuntimeError {}
}

struct NoopWaker();
impl std::task::Wake for NoopWaker {
    fn wake(self: std::sync::Arc<Self>) {
        //nop
    }
}

impl Driver {
    /// Create a [`Driver`] with the given window builder and render target size (if absent, will use the window's inner size instead).
    pub fn new(builder: winit::window::WindowBuilder, render_size: Option<(u32, u32)>) -> Self {
        Self {
            builder,
            render_size,
        }
    }
    /// Kick off the event loop. Once the driver receives the
    /// [`winit::event::Event::Resumed`] event, it will initialize
    /// Frenderer and call `init_cb` with the window and renderer.
    /// This callback may return an application state object or
    /// userdata which will be passed as the final argument to
    /// `handler`, which will be called for every winit event /after/
    /// `init_cb` has been called.  If you don't want `run_event_loop`
    /// to own your application-specific data, you could instead store
    /// such data yourself in internally mutable types such as
    /// [`std::cell::OnceCell`] and evaluate `init_cb` for its side
    /// effects.
    ///
    /// Example:
    /// ```
    ///    drv.run_event_loop::<(), _>(
    ///      move |window, mut frend| {
    ///        let mut camera = Camera2D {
    ///          screen_pos: [0.0, 0.0],
    ///          screen_size: [1024.0, 768.0],
    ///        };
    ///        init_data(&mut frend, &mut camera);
    ///        (window, camera, frend)
    ///      },
    ///      move |event, target, (window, camera, frend)| {
    ///        // handle the winit event here, and maybe do some rendering!
    ///      });
    /// ```
    pub fn run_event_loop<T: 'static, U: 'static>(
        self,
        init_cb: impl FnOnce(std::sync::Arc<winit::window::Window>, crate::Renderer) -> U + 'static,
        mut handler: impl FnMut(winit::event::Event<T>, &winit::event_loop::EventLoopWindowTarget<T>, &mut U)
            + 'static,
    ) -> Result<(), Box<dyn std::error::Error>> {
        enum DriverState<U: 'static> {
            WaitingForResume(winit::window::WindowBuilder),
            PollingFuture(
                Arc<winit::window::Window>,
                #[allow(clippy::type_complexity)]
                std::pin::Pin<
                    Box<
                        dyn std::future::Future<
                            Output = Result<crate::Renderer, Box<dyn std::error::Error>>,
                        >,
                    >,
                >,
            ),
            Running(U),
            // This is just used as a temporary value
            InsideLoop,
        }
        use winit::event_loop::EventLoop;
        let Self {
            builder,
            render_size,
        } = self;
        prepare_logging()?;
        let event_loop: EventLoop<T> =
            winit::event_loop::EventLoopBuilder::with_user_event().build()?;
        let instance = Arc::new(wgpu::Instance::default());
        let waker = Arc::new(NoopWaker()).into();
        let mut init_cb = Some(init_cb);
        let driver_state = std::cell::Cell::new(DriverState::WaitingForResume(builder));
        let cb = move |event, target: &winit::event_loop::EventLoopWindowTarget<_>| {
            target.set_control_flow(winit::event_loop::ControlFlow::Wait);
            driver_state.set(match driver_state.replace(DriverState::InsideLoop) {
                DriverState::WaitingForResume(builder) => {
                    if let winit::event::Event::Resumed = event {
                        let window = Arc::new(builder.build(target).unwrap());
                        prepare_window(&window);
                        let surface = instance.create_surface(Arc::clone(&window)).unwrap();
                        let wsz = window.inner_size();
                        let sz = render_size.unwrap_or((wsz.width, wsz.height));
                        let future = Box::pin(crate::Renderer::with_surface(
                            sz.0,
                            sz.1,
                            wsz.width,
                            wsz.height,
                            Arc::clone(&instance),
                            Some(surface),
                        ));
                        DriverState::PollingFuture(window, future)
                    } else {
                        DriverState::WaitingForResume(builder)
                    }
                }
                DriverState::PollingFuture(window, mut future) => {
                    let mut cx = std::task::Context::from_waker(&waker);
                    if let std::task::Poll::Ready(frend) = future.as_mut().poll(&mut cx) {
                        let frenderer = frend.unwrap();
                        let userdata = init_cb.take().unwrap()(Arc::clone(&window), frenderer);
                        DriverState::Running(userdata)
                    } else {
                        // schedule again
                        target.set_control_flow(winit::event_loop::ControlFlow::Poll);
                        DriverState::PollingFuture(window, future)
                    }
                }
                DriverState::Running(mut userdata) => {
                    handler(event, target, &mut userdata);
                    DriverState::Running(userdata)
                }
                DriverState::InsideLoop => {
                    panic!("driver state loop unexpectedly reentrant");
                }
            });
        };
        #[cfg(not(target_arch = "wasm32"))]
        {
            Ok(event_loop.run(cb)?)
        }
        #[cfg(target_arch = "wasm32")]
        {
            use winit::platform::web::EventLoopExtWebSys;
            Ok(event_loop.spawn(cb))
        }
    }
}

/// If you don't use [`Driver`], it may still be convenient to call
/// `prepare_window` to set up a window in a cross-platform way
/// (e.g. on web, it will add the window's canvas to the HTML
/// document).
#[allow(unused_variables)]
pub fn prepare_window(window: &winit::window::Window) {
    #[cfg(target_arch = "wasm32")]
    {
        use self::web_error::RuntimeError;
        use crate::wgpu::web_sys;
        use winit::platform::web::WindowExtWebSys;
        let doc = web_sys::window()
            .ok_or(RuntimeError::NoBody)
            .unwrap()
            .document()
            .unwrap();
        let canvas = window.canvas().ok_or(RuntimeError::NoCanvas).unwrap();
        doc.body()
            .ok_or(RuntimeError::NoBody)
            .unwrap()
            .append_child(&canvas)
            .unwrap();
    }
}

/// If you don't use [`Driver`], it may still be convenient to call
/// `prepare_logging` to set up `env_logger` or `console_log`
/// appropriately for the platform.
pub fn prepare_logging() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        env_logger::init();
        Ok(())
    }
    #[cfg(target_arch = "wasm32")]
    {
        std::panic::set_hook(Box::new(console_error_panic_hook::hook));
        console_log::init_with_level(log::Level::Warn)?;
        Ok(())
    }
}