1use std::collections::HashSet;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8use gilrs::{Button, Gilrs};
9use winit::application::ApplicationHandler;
10use winit::dpi::LogicalSize;
11use winit::event::{DeviceEvent, DeviceId, ElementState, MouseButton, MouseScrollDelta, WindowEvent};
12use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
13use winit::keyboard::{Key, KeyCode, NamedKey, PhysicalKey};
14use winit::window::{CursorGrabMode, Window, WindowId};
15
16use crate::audio::{AudioOut, AudioSink};
17use crate::gfx::Gfx;
18use crate::host::{self, Game, Gamepad, KEY_STATE_BYTES, Pointer, RawInput, Stop};
19use crate::keymap;
20use crate::present::Present;
21use crate::session::Session;
22
23pub struct Options {
24 pub size: (u32, u32),
26 pub keymap: keymap::Keymap,
27 pub mute: bool,
28 pub present: Present,
30}
31
32pub fn run(session: Session, opts: Options) -> Result<i32, String> {
35 let event_loop = EventLoop::new().map_err(|e| e.to_string())?;
36 let default_title = host::static_title(&session.wasm).unwrap_or_else(|| {
38 std::path::Path::new(&session.name).file_stem().map_or(session.name.clone(), |s| s.to_string_lossy().into_owned())
39 });
40 let mut app = App {
41 title: default_title.clone(),
42 default_title,
43 fps: 0,
44 opts,
45 session: Some(session),
46 window: None,
47 game: None,
48 keys: HashSet::new(),
49 typed: String::new(),
50 key_events: Vec::new(),
51 esc_down: None,
52 mouse: MouseState::default(),
53 applied_mode: 0,
54 pointer_flags: 0,
55 gilrs: Gilrs::new().ok(),
56 next: Instant::now(),
57 fps_t: Instant::now(),
58 fps_n: 0,
59 result: Ok(0),
60 };
61 event_loop.run_app(&mut app).map_err(|e| e.to_string())?;
62 app.result
63}
64
65fn gamepad_pads(gilrs: &mut Gilrs) -> ([u32; 4], usize) {
67 const MAP: &[(Button, u32)] = &[
68 (Button::East, 1 << 0),
69 (Button::South, 1 << 1),
70 (Button::North, 1 << 2),
71 (Button::West, 1 << 3),
72 (Button::LeftTrigger, 1 << 4),
73 (Button::RightTrigger, 1 << 5),
74 (Button::Select, 1 << 6),
75 (Button::Start, 1 << 7),
76 (Button::DPadUp, 1 << 8),
77 (Button::DPadDown, 1 << 9),
78 (Button::DPadLeft, 1 << 10),
79 (Button::DPadRight, 1 << 11),
80 ];
81 while gilrs.next_event().is_some() {}
82 let mut pads = [0u32; 4];
83 let connected = gilrs.gamepads().count();
84 for (i, (_, pad)) in gilrs.gamepads().take(4).enumerate() {
85 pads[i] = MAP.iter().filter(|(b, _)| pad.is_pressed(*b)).fold(0, |m, (_, bit)| m | bit);
86 let (x, y) = (pad.value(gilrs::Axis::LeftStickX), pad.value(gilrs::Axis::LeftStickY));
87 if x < -0.5 { pads[i] |= 1 << 10 }
88 if x > 0.5 { pads[i] |= 1 << 11 }
89 if y > 0.5 { pads[i] |= 1 << 8 }
90 if y < -0.5 { pads[i] |= 1 << 9 }
91 }
92 (pads, connected)
93}
94
95#[derive(Default)]
97struct MouseState {
98 x: f32,
99 y: f32,
100 inside: bool,
101 buttons: u32,
102 pressed: u32,
104 released: u32,
105 dx: f32,
106 dy: f32,
107 wheel_x: f32,
108 wheel_y: f32,
109}
110
111const ESC_HOLD: Duration = Duration::from_millis(1000);
113
114fn mouse_bit(b: MouseButton) -> u32 {
115 match b {
116 MouseButton::Left => 1,
117 MouseButton::Right => 2,
118 MouseButton::Middle => 4,
119 MouseButton::Back => 8,
120 MouseButton::Forward => 16,
121 MouseButton::Other(_) => 0,
122 }
123}
124
125fn gamepads_raw(gilrs: &Gilrs) -> [Gamepad; 4] {
128 use gilrs::{Axis, MappingSource};
129 const BUTTONS: [Button; 17] = [
130 Button::South, Button::East, Button::West, Button::North, Button::LeftTrigger, Button::RightTrigger,
131 Button::LeftTrigger2, Button::RightTrigger2, Button::Select, Button::Start, Button::LeftThumb,
132 Button::RightThumb, Button::DPadUp, Button::DPadDown, Button::DPadLeft, Button::DPadRight, Button::Mode,
133 ];
134 let mut out: [Gamepad; 4] = Default::default();
135 for (slot, (_, pad)) in gilrs.gamepads().take(4).enumerate() {
136 let g = &mut out[slot];
137 g.connected = true;
138 g.name = pad.name().to_owned();
139 if pad.mapping_source() != MappingSource::None {
140 g.standard = true;
141 g.buttons = BUTTONS.iter().map(|b| pad.button_data(*b).map_or(0.0, |d| d.value())).collect();
142 g.axes = vec![pad.value(Axis::LeftStickX), -pad.value(Axis::LeftStickY), pad.value(Axis::RightStickX), -pad.value(Axis::RightStickY)];
144 } else {
145 let mut b: Vec<_> = pad.state().buttons().map(|(c, d)| (c.into_u32(), d.value())).collect();
146 let mut a: Vec<_> = pad.state().axes().map(|(c, d)| (c.into_u32(), d.value())).collect();
147 b.sort_by_key(|x| x.0);
148 a.sort_by_key(|x| x.0);
149 g.buttons = b.into_iter().map(|x| x.1).take(host::GAMEPAD_BUTTONS).collect();
150 g.axes = a.into_iter().map(|x| x.1).take(host::GAMEPAD_AXES).collect();
151 }
152 }
153 out
154}
155
156struct App {
157 title: String,
159 default_title: String,
160 fps: u32,
162 opts: Options,
163 session: Option<Session>,
165 window: Option<Arc<Window>>,
166 game: Option<Game>,
167 keys: HashSet<KeyCode>,
168 typed: String,
170 key_events: Vec<(u16, bool)>,
172 esc_down: Option<Instant>,
174 mouse: MouseState,
175 applied_mode: u32,
177 pointer_flags: u32,
179 gilrs: Option<Gilrs>,
180 next: Instant,
181 fps_t: Instant,
182 fps_n: u32,
183 result: Result<i32, String>,
184}
185
186impl App {
187 fn stop(&mut self, el: &ActiveEventLoop, result: Result<i32, String>) {
188 self.result = result;
189 self.game = None;
192 el.exit();
193 }
194
195 fn show_title(&self) {
199 if let Some(w) = &self.window {
200 w.set_title(&format!("{} — gasm — {} fps", self.title, self.fps));
201 }
202 }
203
204 fn apply_input_mode(&mut self) {
205 let (Some(game), Some(w)) = (&self.game, &self.window) else { return };
206 let want = game.host().input_mode & 6;
207 if want == self.applied_mode {
208 return;
209 }
210 self.applied_mode = want;
211 let locked = want & 4 != 0
212 && w.set_cursor_grab(CursorGrabMode::Locked).or_else(|_| w.set_cursor_grab(CursorGrabMode::Confined)).is_ok();
213 if want & 4 == 0 {
214 let _ = w.set_cursor_grab(CursorGrabMode::None);
215 }
216 w.set_cursor_visible(want == 0);
218 self.pointer_flags = if want != 0 { 2 } else { 0 } | if locked { 4 } else { 0 };
219 }
220
221 fn quit(&mut self, el: &ActiveEventLoop) {
223 if let Some(g) = &mut self.game {
224 g.exit();
225 }
226 self.stop(el, Ok(0));
227 }
228
229 fn tick(&mut self, el: &ActiveEventLoop) {
230 let Some(game) = &mut self.game else { return };
231 let period = Duration::from_secs_f64(1.0 / game.host().frame_rate);
232 let now = Instant::now();
233 if now >= self.next {
234 let behind = ((now - self.next).as_secs_f64() / period.as_secs_f64()) as u32 + 1;
236 let steps = behind.min(4);
237 if self.esc_down.is_some_and(|t| t.elapsed() >= ESC_HOLD) {
239 return self.quit(el);
240 }
241 let mut keys = [0u8; KEY_STATE_BYTES];
242 for &c in &self.keys {
243 let k = keymap::gasm_key(c);
244 if k != 0 {
245 keys[k as usize / 8] |= 1 << (k % 8);
246 }
247 }
248 let (mut pads, gamepads) = self.gilrs.as_mut().map(gamepad_pads).unwrap_or_default();
250 let raw_pads = self.gilrs.as_ref().map(gamepads_raw).unwrap_or_default();
251 if game.host().input_mode & 1 == 0 {
253 let kb = keymap::pads(&self.opts.keymap, &self.keys, gamepads);
254 for (p, k) in pads.iter_mut().zip(kb) {
255 *p |= k;
256 }
257 }
258 let drawable = game.host().gfx.size();
259 for k in 0..steps {
260 let host = game.host_mut();
261 host.pads = pads;
262 let first = k == 0;
264 host.text = Some(if first { std::mem::take(&mut self.typed) } else { String::new() });
265 let m = &mut self.mouse;
266 let pointer = Pointer {
267 x: m.x,
268 y: m.y,
269 dx: if first { m.dx } else { 0.0 },
270 dy: if first { m.dy } else { 0.0 },
271 wheel_x: if first { m.wheel_x } else { 0.0 },
272 wheel_y: if first { m.wheel_y } else { 0.0 },
273 buttons: m.buttons,
274 pressed: if first { m.pressed } else { 0 },
275 released: if first { m.released } else { 0 },
276 flags: m.inside as u32 | self.pointer_flags,
277 drawable: (drawable.0 as f32, drawable.1 as f32),
278 integer_scale: self.opts.present.integer_scale,
279 };
280 if first {
281 (m.dx, m.dy, m.wheel_x, m.wheel_y, m.pressed, m.released) = (0.0, 0.0, 0.0, 0.0, 0, 0);
282 }
283 if first {
284 host.input = RawInput { keys: Some(keys), key_events: std::mem::take(&mut self.key_events), pointer: Some(pointer), gamepads: Some(raw_pads.clone()) };
285 } else {
286 host.input.key_events.clear();
287 host.input.pointer = Some(pointer);
288 }
289 host.show_frame = k + 1 == steps;
290 host.gfx.used = false;
291 match game.frame() {
292 Ok(()) => {}
293 Err(Stop::Exit(code)) => return self.stop(el, Ok(code)),
294 Err(Stop::Trap(e)) => return self.stop(el, Err(e)),
295 }
296 self.next += period;
297 self.fps_n += 1;
298 }
299 if behind > 4 {
300 self.next = Instant::now() + period;
301 }
302 let host = game.host_mut();
304 if !host.gfx.used && host.width > 0 {
305 let (w, h) = (host.width as u32, host.height as u32);
306 let rgba = std::mem::take(&mut host.rgba);
307 host.gfx.present_video(&rgba, w, h, host.aspect);
308 host.rgba = rgba;
309 }
310 if std::mem::take(&mut host.title_changed) {
311 self.title = host.title.clone().unwrap_or_else(|| self.default_title.clone());
312 self.show_title();
313 }
314 }
315 self.apply_input_mode();
316 if self.fps_t.elapsed() >= Duration::from_secs(1) {
317 self.fps = self.fps_n;
318 self.show_title();
319 self.fps_n = 0;
320 self.fps_t = Instant::now();
321 }
322 el.set_control_flow(ControlFlow::WaitUntil(self.next));
323 }
324}
325
326impl ApplicationHandler for App {
327 fn resumed(&mut self, el: &ActiveEventLoop) {
328 if self.window.is_some() {
329 return;
330 }
331 let attrs = Window::default_attributes()
332 .with_title(format!("{} — gasm", self.title))
333 .with_inner_size(LogicalSize::new(self.opts.size.0, self.opts.size.1));
334 let window = match el.create_window(attrs) {
335 Ok(w) => Arc::new(w),
336 Err(e) => return self.stop(el, Err(format!("cannot create window: {e}"))),
337 };
338 let gfx = match Gfx::for_window(window.clone()) {
339 Ok(mut g) => {
340 g.present = self.opts.present;
341 g
342 }
343 Err(e) => return self.stop(el, Err(format!("cannot initialise GPU: {e}"))),
344 };
345 let audio: Option<Box<dyn AudioOut>> = if self.opts.mute {
346 None
347 } else {
348 match AudioSink::open() {
349 Ok(a) => {
350 eprintln!("[gasm] audio: {} Hz", a.device_rate());
351 Some(Box::new(a))
352 }
353 Err(e) => {
354 eprintln!("[gasm] audio disabled: {e}");
355 None
356 }
357 }
358 };
359 let Some(session) = self.session.take() else { return };
360 match session.start(audio, gfx, false, false) {
361 Ok(g) => self.game = Some(g),
362 Err(Stop::Exit(code)) => return self.stop(el, Ok(code)),
363 Err(Stop::Trap(e)) => return self.stop(el, Err(e)),
364 }
365 self.window = Some(window);
366 self.next = Instant::now();
367 el.set_control_flow(ControlFlow::WaitUntil(self.next));
368 }
369
370 fn window_event(&mut self, el: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
371 match event {
372 WindowEvent::CloseRequested => self.quit(el),
373 WindowEvent::Resized(size) => {
374 if let Some(g) = &mut self.game {
375 g.host_mut().gfx.resize(size.width, size.height);
376 }
377 }
378 WindowEvent::Focused(false) => {
379 for c in self.keys.drain() {
381 let k = keymap::gasm_key(c);
382 if k != 0 {
383 self.key_events.push((k, false));
384 }
385 }
386 self.esc_down = None;
387 self.mouse.inside = false;
388 self.mouse.released |= self.mouse.buttons;
389 self.mouse.buttons = 0;
390 }
391 WindowEvent::CursorMoved { position, .. } => {
392 self.mouse.x = position.x as f32;
393 self.mouse.y = position.y as f32;
394 self.mouse.inside = true;
395 }
396 WindowEvent::CursorEntered { .. } => self.mouse.inside = true,
397 WindowEvent::CursorLeft { .. } => self.mouse.inside = false,
398 WindowEvent::MouseInput { state, button, .. } => {
399 let bit = mouse_bit(button);
400 match state {
401 ElementState::Pressed => {
402 self.mouse.buttons |= bit;
403 self.mouse.pressed |= bit;
404 }
405 ElementState::Released => {
406 self.mouse.buttons &= !bit;
407 self.mouse.released |= bit;
408 }
409 }
410 }
411 WindowEvent::MouseWheel { delta, .. } => {
412 let (x, y) = match delta {
414 MouseScrollDelta::LineDelta(x, y) => (x, y),
415 MouseScrollDelta::PixelDelta(p) => (p.x as f32 / 100.0, p.y as f32 / 100.0),
416 };
417 self.mouse.wheel_x -= x;
418 self.mouse.wheel_y -= y;
419 }
420 WindowEvent::KeyboardInput { event, .. } => {
421 if let PhysicalKey::Code(code) = event.physical_key {
422 if code == KeyCode::Escape && !event.repeat {
423 self.esc_down = (event.state == ElementState::Pressed).then(Instant::now);
424 }
425 let k = keymap::gasm_key(code);
426 if !event.repeat && k != 0 {
427 self.key_events.push((k, event.state == ElementState::Pressed));
428 }
429 match event.state {
430 ElementState::Pressed => self.keys.insert(code),
431 ElementState::Released => self.keys.remove(&code),
432 };
433 }
434 if event.state == ElementState::Pressed {
435 match &event.logical_key {
436 Key::Named(NamedKey::Enter) => self.typed.push('\n'),
437 Key::Named(NamedKey::Backspace) => self.typed.push('\u{8}'),
438 _ => {
439 if let Some(t) = &event.text {
440 self.typed.extend(t.chars().filter(|c| !c.is_control()));
441 }
442 }
443 }
444 }
445 }
446 WindowEvent::RedrawRequested => self.tick(el),
447 _ => {}
448 }
449 }
450
451 fn device_event(&mut self, _el: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
452 if let DeviceEvent::MouseMotion { delta } = event
454 && self.window.as_ref().is_some_and(|w| w.has_focus())
455 {
456 self.mouse.dx += delta.0 as f32;
457 self.mouse.dy += delta.1 as f32;
458 }
459 }
460
461 fn about_to_wait(&mut self, el: &ActiveEventLoop) {
462 self.tick(el);
463 }
464}
465