1
use std::collections::HashMap;
2
use std::sync::atomic::Ordering;
3

            
4
use kludgine_core::flume;
5
use kludgine_core::winit::event::Event;
6
use kludgine_core::winit::event_loop::EventLoopProxy;
7
use kludgine_core::winit::window::{Theme, WindowId};
8
use kludgine_core::winit::{self};
9
use parking_lot::{MappedRwLockReadGuard, Mutex, RwLock, RwLockReadGuard};
10

            
11
use crate::application::Application;
12
use crate::window::{
13
    opened_first_window, RuntimeWindow, RuntimeWindowConfig, Window, WindowBuilder,
14
};
15

            
16
pub enum RuntimeRequest {
17
    #[cfg(feature = "multiwindow")]
18
    OpenWindow {
19
        builder: WindowBuilder,
20
        window_sender: flume::Sender<RuntimeWindowConfig>,
21
    },
22
    WindowClosed,
23
    Quit,
24
}
25

            
26
impl RuntimeRequest {
27
    pub fn send(self) {
28
        let guard = GLOBAL_RUNTIME_SENDER.lock();
29
        match *guard {
30
            Some(ref sender) => {
31
                let _: Result<_, _> = sender.send_event(self);
32
            }
33
            None => panic!("Uninitialized runtime"),
34
        }
35
    }
36
}
37

            
38
#[derive(Debug)]
39
pub enum RuntimeEvent {
40
    Running,
41
    WindowClosed,
42
}
43

            
44
use kludgine_core::lazy_static::lazy_static;
45

            
46
pub trait EventProcessor: Send + Sync {
47
    fn process_event(
48
        &mut self,
49
        event_loop: &winit::event_loop::EventLoopWindowTarget<RuntimeRequest>,
50
        event: winit::event::Event<'_, RuntimeRequest>,
51
        control_flow: &mut winit::event_loop::ControlFlow,
52
    );
53
}
54

            
55
lazy_static! {
56
    pub static ref GLOBAL_RUNTIME_SENDER: Mutex<Option<EventLoopProxy<RuntimeRequest>>> =
57
        Mutex::new(None);
58
    pub static ref GLOBAL_RUNTIME_RECEIVER: Mutex<Option<flume::Receiver<RuntimeEvent>>> =
59
        Mutex::new(None);
60
    pub static ref GLOBAL_EVENT_HANDLER: Mutex<Option<Box<dyn EventProcessor>>> = Mutex::new(None);
61
}
62

            
63
#[cfg(feature = "smol")]
64
mod smol;
65

            
66
#[cfg(feature = "tokio")]
67
mod tokio;
68

            
69
#[cfg(target_arch = "wasm32")]
70
mod web_sys;
71

            
72
pub struct ApplicationRuntime<App> {
73
    app: App,
74
}
75

            
76
impl<App> ApplicationRuntime<App>
77
where
78
    App: Application + 'static,
79
{
80
    fn launch(self) -> flume::Sender<RuntimeEvent> {
81
        let (event_sender, event_receiver) = flume::unbounded();
82
        {
83
            let mut global_receiver = GLOBAL_RUNTIME_RECEIVER.lock();
84
            assert!(global_receiver.is_none());
85
            *global_receiver = Some(event_receiver);
86
        }
87

            
88
        std::thread::Builder::new()
89
            .name(String::from("kludgine-app"))
90
            .spawn(move || self.async_main())
91
            .unwrap();
92

            
93
        event_sender
94
    }
95

            
96
    fn async_main(mut self)
97
    where
98
        App: Application + 'static,
99
    {
100
        self.app.initialize();
101

            
102
        self.run();
103
    }
104

            
105
    fn run(mut self)
106
    where
107
        App: Application + 'static,
108
    {
109
        let mut running = false;
110
        let event_receiver = {
111
            let guard = GLOBAL_RUNTIME_RECEIVER.lock();
112
            guard.as_ref().expect("Receiver was not set").clone()
113
        };
114
        while let Ok(event) = event_receiver.recv() {
115
            match event {
116
                RuntimeEvent::Running => {
117
                    running = true;
118
                }
119
                RuntimeEvent::WindowClosed => {}
120
            }
121

            
122
            if running && self.app.should_exit() {
123
                RuntimeRequest::Quit.send();
124
                break;
125
            }
126
        }
127
    }
128
}
129

            
130
impl EventProcessor for Runtime {
131
    #[cfg_attr(not(feature = "multiwindow"), allow(unused_variables))] // event_loop is unused if this feature isn't specified
132
    fn process_event(
133
        &mut self,
134
        event_loop: &winit::event_loop::EventLoopWindowTarget<RuntimeRequest>,
135
        event: winit::event::Event<'_, RuntimeRequest>,
136
        control_flow: &mut winit::event_loop::ControlFlow,
137
    ) {
138
        // while let Ok(request) = self.request_receiver.try_recv() {
139
        //
140
        // }
141
        Self::try_process_window_events(Some(&event));
142

            
143
        match event {
144
            winit::event::Event::NewEvents(winit::event::StartCause::Init) => {
145
                self.event_sender
146
                    .send(RuntimeEvent::Running)
147
                    .unwrap_or_default();
148
            }
149
            winit::event::Event::UserEvent(request) => match request {
150
                #[cfg(feature = "multiwindow")]
151
                RuntimeRequest::OpenWindow {
152
                    window_sender,
153
                    builder,
154
                } => {
155
                    Self::internal_open_window(&window_sender, builder, event_loop);
156
                }
157
                RuntimeRequest::Quit => {
158
                    *control_flow = winit::event_loop::ControlFlow::Exit;
159
                    return;
160
                }
161
                RuntimeRequest::WindowClosed => {
162
                    self.event_sender
163
                        .send(RuntimeEvent::WindowClosed)
164
                        .unwrap_or_default();
165
                }
166
            },
167
            _ => {}
168
        }
169

            
170
        *control_flow = winit::event_loop::ControlFlow::Wait;
171
    }
172
}
173

            
174
/// Runtime is designed to consume the main thread. For cross-platform
175
/// compatibility, ensure that you call [`Runtime::run()`] from thee main
176
/// thread.
177
pub struct Runtime {
178
    event_sender: flume::Sender<RuntimeEvent>,
179
}
180

            
181
lazy_static! {
182
    pub static ref WINIT_WINDOWS: RwLock<HashMap<WindowId, winit::window::Window>> =
183
        RwLock::new(HashMap::new());
184
}
185

            
186
impl Runtime {
187
    /// Initializes the managed async runtime.
188
    pub fn initialize() {
189
        initialize_async_runtime();
190
    }
191

            
192
    /// Returns a new runtime for `app`.
193
    pub fn new<App>(app: App) -> Self
194
    where
195
        App: Application + 'static,
196
    {
197
        Self::initialize();
198

            
199
        let app_runtime = ApplicationRuntime { app };
200
        let event_sender = app_runtime.launch();
201

            
202
        Self { event_sender }
203
    }
204

            
205
    #[cfg(feature = "multiwindow")]
206
    fn internal_open_window(
207
        window_sender: &flume::Sender<RuntimeWindowConfig>,
208
        builder: WindowBuilder,
209
        event_loop: &winit::event_loop::EventLoopWindowTarget<RuntimeRequest>,
210
    ) {
211
        let builder: winit::window::WindowBuilder = builder.into();
212
        let winit_window = builder.build(event_loop).unwrap();
213
        window_sender
214
            .try_send(
215
                RuntimeWindowConfig::new(&winit_window).expect("failed to create window surface"),
216
            )
217
            .unwrap();
218

            
219
        let mut windows = WINIT_WINDOWS.write();
220
        windows.insert(winit_window.id(), winit_window);
221
    }
222

            
223
    const fn should_run_in_exclusive_mode() -> bool {
224
        cfg!(any(target_os = "android", target_os = "ios"))
225
    }
226

            
227
    /// Executes the runtime's event loop.
228
    pub fn run<T>(self, initial_window: WindowBuilder, window: T) -> !
229
    where
230
        T: Window + Sized + 'static,
231
    {
232
        let event_loop =
233
            winit::event_loop::EventLoopBuilder::<RuntimeRequest>::with_user_event().build();
234
        let initial_system_theme = initial_window.initial_system_theme.unwrap_or(Theme::Light);
235
        let mut initial_window: winit::window::WindowBuilder = initial_window.into();
236

            
237
        if Self::should_run_in_exclusive_mode() {
238
            let mut exclusive_mode = None;
239
            for monitor in event_loop.available_monitors() {
240
                for mode in monitor.video_modes() {
241
                    exclusive_mode = Some(mode); // TODO pick the best mode, not
242
                                                 // the last
243
                }
244
            }
245

            
246
            initial_window = initial_window.with_fullscreen(Some(
247
                winit::window::Fullscreen::Exclusive(exclusive_mode.unwrap()),
248
            ));
249
        }
250
        let initial_window = initial_window.build(&event_loop).unwrap();
251
        #[cfg(target_arch = "wasm32")]
252
        {
253
            use winit::platform::web::WindowExtWebSys;
254
            // On wasm, append the canvas to the document body
255
            ::web_sys::window()
256
                .and_then(|win| win.document())
257
                .and_then(|doc| doc.body())
258
                .and_then(|body| {
259
                    body.append_child(&::web_sys::Element::from(initial_window.canvas()))
260
                        .ok()
261
                })
262
                .expect("couldn't append canvas to document body");
263
        }
264
        let (window_sender, window_receiver) = flume::bounded(1);
265
        window_sender
266
            .send(RuntimeWindowConfig::new(&initial_window).expect("failed to create surface"))
267
            .unwrap();
268

            
269
        RuntimeWindow::open(&window_receiver, initial_system_theme, window);
270

            
271
        {
272
            let mut windows = WINIT_WINDOWS.write();
273
            windows.insert(initial_window.id(), initial_window);
274
        }
275

            
276
        {
277
            let mut global_sender = GLOBAL_RUNTIME_SENDER.lock();
278
            assert!(global_sender.is_none());
279
            *global_sender = Some(event_loop.create_proxy());
280
        }
281

            
282
        // Install the global event handler, and also ensure we aren't trying to
283
        // initialize two runtimes This is necessary because EventLoop::run requires the
284
        // function/closure passed to have a `static lifetime for valid reasons. Every
285
        // approach at using only local variables I could not solve, so we wrap it in a
286
        // mutex. This abstraction also wraps it in dynamic dispatch, because we can't
287
        // have a generic-type static variable.
288
        {
289
            let mut event_handler = GLOBAL_EVENT_HANDLER.lock();
290
            assert!(event_handler.is_none());
291
            *event_handler = Some(Box::new(self));
292
        }
293
        event_loop.run(move |event, event_loop, control_flow| {
294
            let mut event_handler_guard = GLOBAL_EVENT_HANDLER.lock();
295
            let event_handler = event_handler_guard
296
                .as_mut()
297
                .expect("No event handler installed");
298
            event_handler
299
                .as_mut()
300
                .process_event(event_loop, event, control_flow);
301
        });
302
    }
303

            
304
    /// Opens a [`Window`]. Requires feature `multiwindow`.
305
    #[cfg(feature = "multiwindow")]
306
    pub fn open_window<T>(builder: WindowBuilder, window: T)
307
    where
308
        T: Window + Sized,
309
    {
310
        let (window_sender, window_receiver) = flume::bounded(1);
311
        let initial_system_theme = builder.initial_system_theme.unwrap_or(Theme::Light);
312
        RuntimeRequest::OpenWindow {
313
            builder,
314
            window_sender,
315
        }
316
        .send();
317

            
318
        RuntimeWindow::open(&window_receiver, initial_system_theme, window);
319
    }
320

            
321
    pub(crate) fn try_process_window_events(event: Option<&Event<'_, RuntimeRequest>>) -> bool {
322
        let mut windows = match WINDOWS.try_write() {
323
            Some(guard) => guard,
324
            None => return false,
325
        };
326

            
327
        match event {
328
            Some(Event::WindowEvent { window_id, event }) => {
329
                if let Some(window) = windows.get_mut(window_id) {
330
                    window.process_event(event);
331
                }
332
            }
333
            Some(Event::RedrawRequested(window_id)) => {
334
                if let Some(window) = windows.get_mut(window_id) {
335
                    window.request_redraw();
336
                }
337
            }
338
            _ => {}
339
        }
340

            
341
        {
342
            for window in windows.values_mut() {
343
                window.receive_messages();
344
            }
345
        }
346

            
347
        if opened_first_window() {
348
            let mut winit_windows = WINIT_WINDOWS.write();
349
            windows.retain(|id, w| {
350
                if w.keep_running.load(Ordering::SeqCst) {
351
                    true
352
                } else {
353
                    winit_windows.remove(id);
354
                    false
355
                }
356
            });
357
        }
358

            
359
        true
360
    }
361

            
362
    pub(crate) fn winit_window(
363
        id: WindowId,
364
    ) -> Option<MappedRwLockReadGuard<'static, winit::window::Window>> {
365
        let windows = WINIT_WINDOWS.read();
366
        RwLockReadGuard::try_map(windows, |windows| windows.get(&id)).ok()
367
    }
368
}
369

            
370
fn initialize_async_runtime() {
371
    #[cfg(feature = "smol")]
372
    smol::initialize();
373
    #[cfg(all(feature = "tokio", not(feature = "smol")))]
374
    tokio::initialize();
375
}
376

            
377
lazy_static! {
378
    pub static ref WINDOWS: RwLock<HashMap<WindowId, RuntimeWindow>> = RwLock::new(HashMap::new());
379
}