From f60b10ce1de6987229b7c8a4976ba3a989d07c1e Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 18 Nov 2024 22:49:56 -0500 Subject: [PATCH] Implement gamepad input --- Cargo.lock | 127 +++++++++++++++++++++++++-- Cargo.toml | 1 + src/app.rs | 60 +++++++++---- src/app/input.rs | 26 +++--- src/controller.rs | 144 +++++++++++++++++++++++------- src/input.rs | 217 +++++++++++++++++++++++++++++++--------------- 6 files changed, 435 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ada172..b47fc70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "foreign-types", "libc", @@ -447,7 +447,7 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "libc", "objc", @@ -498,6 +498,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -511,7 +521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "core-graphics-types", "foreign-types", "libc", @@ -524,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "libc", ] @@ -668,6 +678,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.5.0" @@ -716,6 +732,40 @@ dependencies = [ "wasi", ] +[[package]] +name = "gilrs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "495af945e45efd6386227613cd9fb7bd7c43d3c095040e30c5304c489e6abed5" +dependencies = [ + "core-foundation 0.10.0", + "inotify", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.58.0", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -905,6 +955,36 @@ dependencies = [ "hashbrown 0.15.1", ] +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.6.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1013,6 +1093,16 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1131,7 +1221,7 @@ dependencies = [ "ascii", "block", "cocoa", - "core-foundation", + "core-foundation 0.9.4", "dirs-next", "objc", "objc-foundation", @@ -1198,6 +1288,18 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1910,6 +2012,7 @@ dependencies = [ "cc", "clap", "cpal", + "gilrs", "imgui", "imgui-wgpu", "imgui-winit-support", @@ -2175,6 +2278,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -2875,7 +2990,7 @@ dependencies = [ "calloop", "cfg_aliases 0.2.1", "concurrent-queue", - "core-foundation", + "core-foundation 0.9.4", "core-graphics", "cursor-icon", "dpi", diff --git a/Cargo.toml b/Cargo.toml index 5b62f02..50b667c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ bitflags = "2" bytemuck = { version = "1", features = ["derive"] } clap = { version = "4", features = ["derive"] } cpal = "0.15" +gilrs = "0.11" imgui = { version = "0.12", features = ["tables-api"] } imgui-wgpu = { git = "https://github.com/SupernaviX/imgui-wgpu-rs", rev = "5bb8673" } imgui-winit-support = "0.13" diff --git a/src/app.rs b/src/app.rs index 91b9381..5213243 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,7 @@ -use std::{ - collections::HashMap, - fmt::Debug, - sync::{Arc, RwLock}, -}; +use std::{collections::HashMap, fmt::Debug, thread}; use game::GameWindow; +use gilrs::{EventType, Gilrs}; use input::InputWindow; use winit::{ application::ApplicationHandler, @@ -14,9 +11,9 @@ use winit::{ }; use crate::{ - controller::ControllerState, - emulator::{EmulatorClient, EmulatorCommand, SimId}, - input::InputMapper, + controller::ControllerManager, + emulator::{EmulatorClient, SimId}, + input::MappingProvider, }; mod common; @@ -26,21 +23,26 @@ mod input; pub struct App { windows: HashMap>, client: EmulatorClient, - input_mapper: Arc>, - controller: ControllerState, + mappings: MappingProvider, + controllers: ControllerManager, proxy: EventLoopProxy, player_2_window: Option, } impl App { pub fn new(client: EmulatorClient, proxy: EventLoopProxy) -> Self { - let input_mapper = Arc::new(RwLock::new(InputMapper::new())); - let controller = ControllerState::new(input_mapper.clone()); + let mappings = MappingProvider::new(); + let controllers = ControllerManager::new(client.clone(), &mappings); + { + let mappings = mappings.clone(); + let proxy = proxy.clone(); + thread::spawn(|| process_gamepad_input(mappings, proxy)); + } Self { windows: HashMap::new(), client, - input_mapper, - controller, + mappings, + controllers, proxy, player_2_window: None, } @@ -65,10 +67,7 @@ impl ApplicationHandler for App { event: WindowEvent, ) { if let WindowEvent::KeyboardInput { event, .. } = &event { - if let Some((sim_id, pressed)) = self.controller.key_event(event) { - self.client - .send_command(EmulatorCommand::SetKeys(sim_id, pressed)); - } + self.controllers.handle_key_event(event); } let Some(window) = self.windows.get_mut(&window_id) else { return; @@ -80,7 +79,7 @@ impl ApplicationHandler for App { match event { UserEvent::OpenInputWindow => { let window = - InputWindow::new(event_loop, self.input_mapper.clone(), self.proxy.clone()); + InputWindow::new(event_loop, self.mappings.clone(), self.proxy.clone()); self.windows.insert(window.id(), Box::new(window)); } UserEvent::OpenPlayer2Window => { @@ -102,6 +101,9 @@ impl ApplicationHandler for App { } self.windows.remove(&window_id); } + UserEvent::GamepadEvent(event) => { + self.controllers.handle_gamepad_event(&event); + } } } @@ -139,4 +141,24 @@ pub enum UserEvent { OpenInputWindow, OpenPlayer2Window, Close(WindowId), + GamepadEvent(gilrs::Event), +} + +fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy) { + let Ok(mut gilrs) = Gilrs::new() else { + eprintln!("could not connect gamepad listener"); + return; + }; + while let Some(event) = gilrs.next_event_blocking(None) { + if event.event == EventType::Connected { + let Some(gamepad) = gilrs.connected_gamepad(event.id) else { + continue; + }; + mappings.map_gamepad(SimId::Player1, &gamepad); + } + if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() { + // main thread has closed! we done + return; + } + } } diff --git a/src/app/input.rs b/src/app/input.rs index b39f8b1..8147f5c 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, - time::Instant, -}; +use std::time::Instant; use winit::{ dpi::LogicalSize, @@ -11,7 +8,7 @@ use winit::{ use crate::{ emulator::{SimId, VBKey}, - input::InputMapper, + input::MappingProvider, }; use super::{ @@ -22,7 +19,7 @@ use super::{ pub struct InputWindow { window: WindowState, imgui: ImguiState, - input_mapper: Arc>, + mappings: MappingProvider, proxy: EventLoopProxy, now_binding: Option<(SimId, VBKey)>, } @@ -47,7 +44,7 @@ const KEY_NAMES: [(VBKey, &str); 14] = [ impl InputWindow { pub fn new( event_loop: &ActiveEventLoop, - input_mapper: Arc>, + mappings: MappingProvider, proxy: EventLoopProxy, ) -> Self { let window = WindowStateBuilder::new(event_loop) @@ -58,7 +55,7 @@ impl InputWindow { Self { window, imgui, - input_mapper, + mappings, now_binding: None, proxy, } @@ -89,10 +86,11 @@ impl InputWindow { let ui = context.new_frame(); let mut render_key_bindings = |sim_id: SimId| { + let mappings = self.mappings.for_sim(sim_id); if let Some(table) = ui.begin_table("controls", 2) { let binding_names = { - let mapper = self.input_mapper.read().unwrap(); - mapper.binding_names(&sim_id) + let mapping = mappings.read().unwrap(); + mapping.keyboard_mapping_names() }; ui.table_next_row(); @@ -115,8 +113,8 @@ impl InputWindow { }); ui.same_line(); if ui.button(format!("Clear##{name}")) { - let mut mapper = self.input_mapper.write().unwrap(); - mapper.clear_binding(sim_id, key); + let mut mapping = mappings.write().unwrap(); + mapping.clear_keyboard_mappings(key); } } @@ -185,8 +183,8 @@ impl InputWindow { let Some((sim_id, vb)) = self.now_binding.take() else { return; }; - let mut mapper = self.input_mapper.write().unwrap(); - mapper.bind_key(sim_id, vb, event.physical_key); + let mut mappings = self.mappings.for_sim(sim_id).write().unwrap(); + mappings.add_keyboard_mapping(vb, event.physical_key); } } diff --git a/src/controller.rs b/src/controller.rs index d10a1ab..fe2ec14 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,48 +1,128 @@ use std::sync::{Arc, RwLock}; -use winit::event::{ElementState, KeyEvent}; - -use crate::{ - emulator::{SimId, VBKey}, - input::InputMapper, +use gilrs::{ev::Code, Event as GamepadEvent, EventType, GamepadId}; +use winit::{ + event::{ElementState, KeyEvent}, + keyboard::PhysicalKey, }; -pub struct ControllerState { - input_mapper: Arc>, - pressed: Vec, +use crate::{ + emulator::{EmulatorClient, EmulatorCommand, SimId, VBKey}, + input::{InputMapping, MappingProvider}, +}; + +pub struct Controller { + pub sim_id: SimId, + state: VBKey, + mapping: Arc>, } -impl ControllerState { - pub fn new(input_mapper: Arc>) -> Self { +impl Controller { + pub fn new(sim_id: SimId, mappings: &MappingProvider) -> Self { Self { - input_mapper, - pressed: vec![VBKey::SGN; 2], + sim_id, + state: VBKey::SGN, + mapping: mappings.for_sim(sim_id).clone(), } } - pub fn key_event(&mut self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - let (sim_id, input) = self.key_event_to_input(event)?; - let pressed = &mut self.pressed[sim_id.to_index()]; + pub fn key_event(&mut self, event: &KeyEvent) -> Option { + let keys = self.map_keys(&event.physical_key)?; match event.state { - ElementState::Pressed => { - if pressed.contains(input) { - return None; - } - pressed.insert(input); - Some((sim_id, *pressed)) - } - ElementState::Released => { - if !pressed.contains(input) { - return None; - } - pressed.remove(input); - Some((sim_id, *pressed)) - } + ElementState::Pressed => self.update_state(keys, VBKey::empty()), + ElementState::Released => self.update_state(VBKey::empty(), keys), } } - fn key_event_to_input(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - let mapper = self.input_mapper.read().unwrap(); - mapper.key_event(event) + pub fn gamepad_event(&mut self, event: &GamepadEvent) -> Option { + let (pressed, released) = match event.event { + EventType::ButtonPressed(_, code) => { + let mappings = self.map_button(&event.id, &code)?; + (mappings, VBKey::empty()) + } + EventType::ButtonReleased(_, code) => { + let mappings = self.map_button(&event.id, &code)?; + (VBKey::empty(), mappings) + } + EventType::AxisChanged(_, value, code) => { + let (neg, pos) = self.map_axis(&event.id, &code)?; + let mut pressed = VBKey::empty(); + let mut released = VBKey::empty(); + if value < -0.75 { + pressed = pressed.union(neg); + } + if value > 0.75 { + pressed = pressed.union(pos); + } + if value > -0.65 { + released = released.union(neg); + } + if value < 0.65 { + released = released.union(pos); + } + (pressed, released) + } + _ => { + return None; + } + }; + self.update_state(pressed, released) + } + + fn update_state(&mut self, pressed: VBKey, released: VBKey) -> Option { + let old_state = self.state; + self.state = self.state.union(pressed).difference(released); + if self.state != old_state { + Some(self.state) + } else { + None + } + } + + fn map_keys(&self, key: &PhysicalKey) -> Option { + self.mapping.read().unwrap().map_keyboard(key) + } + + fn map_button(&self, id: &GamepadId, code: &Code) -> Option { + self.mapping.read().unwrap().map_button(id, code) + } + + fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + self.mapping.read().unwrap().map_axis(id, code) + } +} + +pub struct ControllerManager { + client: EmulatorClient, + controllers: [Controller; 2], +} + +impl ControllerManager { + pub fn new(client: EmulatorClient, mappings: &MappingProvider) -> Self { + Self { + client, + controllers: [ + Controller::new(SimId::Player1, mappings), + Controller::new(SimId::Player2, mappings), + ], + } + } + + pub fn handle_key_event(&mut self, event: &KeyEvent) { + for controller in &mut self.controllers { + if let Some(pressed) = controller.key_event(event) { + self.client + .send_command(EmulatorCommand::SetKeys(controller.sim_id, pressed)); + } + } + } + + pub fn handle_gamepad_event(&mut self, event: &GamepadEvent) { + for controller in &mut self.controllers { + if let Some(pressed) = controller.gamepad_event(event) { + self.client + .send_command(EmulatorCommand::SetKeys(controller.sim_id, pressed)); + } + } } } diff --git a/src/input.rs b/src/input.rs index 44e626e..a5364a5 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,88 +1,167 @@ -use std::collections::HashMap; - -use winit::{ - event::KeyEvent, - keyboard::{KeyCode, PhysicalKey}, +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::{Arc, RwLock}, }; +use gilrs::{ev::Code, Axis, Button, Gamepad, GamepadId}; +use winit::keyboard::{KeyCode, PhysicalKey}; + use crate::emulator::{SimId, VBKey}; -pub struct InputMapper { - vb_bindings: HashMap>, - key_bindings: HashMap, +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +struct DeviceId(u16, u16); + +pub struct GamepadMapping { + buttons: HashMap, + axes: HashMap, } -impl InputMapper { - pub fn new() -> Self { - let mut mapper = Self { - vb_bindings: HashMap::new(), - key_bindings: HashMap::new(), +impl GamepadMapping { + fn for_gamepad(gamepad: &Gamepad) -> Self { + let mut buttons = HashMap::new(); + let mut default_button = |btn: Button, key: VBKey| { + if let Some(code) = gamepad.button_code(btn) { + buttons.insert(code, key); + } }; - mapper.bind_key(SimId::Player1, VBKey::SEL, PhysicalKey::Code(KeyCode::KeyA)); - mapper.bind_key(SimId::Player1, VBKey::STA, PhysicalKey::Code(KeyCode::KeyS)); - mapper.bind_key(SimId::Player1, VBKey::B, PhysicalKey::Code(KeyCode::KeyD)); - mapper.bind_key(SimId::Player1, VBKey::A, PhysicalKey::Code(KeyCode::KeyF)); - mapper.bind_key(SimId::Player1, VBKey::LT, PhysicalKey::Code(KeyCode::KeyE)); - mapper.bind_key(SimId::Player1, VBKey::RT, PhysicalKey::Code(KeyCode::KeyR)); - mapper.bind_key(SimId::Player1, VBKey::RU, PhysicalKey::Code(KeyCode::KeyI)); - mapper.bind_key(SimId::Player1, VBKey::RL, PhysicalKey::Code(KeyCode::KeyJ)); - mapper.bind_key(SimId::Player1, VBKey::RD, PhysicalKey::Code(KeyCode::KeyK)); - mapper.bind_key(SimId::Player1, VBKey::RR, PhysicalKey::Code(KeyCode::KeyL)); - mapper.bind_key( - SimId::Player1, - VBKey::LU, - PhysicalKey::Code(KeyCode::ArrowUp), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LL, - PhysicalKey::Code(KeyCode::ArrowLeft), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LD, - PhysicalKey::Code(KeyCode::ArrowDown), - ); - mapper.bind_key( - SimId::Player1, - VBKey::LR, - PhysicalKey::Code(KeyCode::ArrowRight), - ); - mapper + default_button(Button::South, VBKey::A); + default_button(Button::West, VBKey::B); + default_button(Button::RightTrigger, VBKey::RT); + default_button(Button::LeftTrigger, VBKey::LT); + default_button(Button::Start, VBKey::STA); + default_button(Button::Select, VBKey::SEL); + + let mut axes = HashMap::new(); + let mut default_axis = |axis: Axis, neg: VBKey, pos: VBKey| { + if let Some(code) = gamepad.axis_code(axis) { + axes.insert(code, (neg, pos)); + } + }; + default_axis(Axis::LeftStickX, VBKey::LL, VBKey::LR); + default_axis(Axis::LeftStickY, VBKey::LD, VBKey::LU); + default_axis(Axis::RightStickX, VBKey::RL, VBKey::RR); + default_axis(Axis::RightStickY, VBKey::RD, VBKey::RU); + default_axis(Axis::DPadX, VBKey::LL, VBKey::LR); + default_axis(Axis::DPadY, VBKey::LD, VBKey::LU); + + Self { buttons, axes } + } +} + +#[derive(Default)] +pub struct InputMapping { + keys: HashMap, + gamepads: HashMap>>, +} + +impl InputMapping { + pub fn map_keyboard(&self, key: &PhysicalKey) -> Option { + self.keys.get(key).copied() } - pub fn binding_names(&self, sim_id: &SimId) -> HashMap { - let Some(bindings) = self.vb_bindings.get(sim_id) else { - return HashMap::new(); - }; - bindings - .iter() - .map(|(k, v)| { - let name = match v { - PhysicalKey::Code(code) => format!("{code:?}"), - k => format!("{:?}", k), - }; - (*k, name) + pub fn map_button(&self, id: &GamepadId, code: &Code) -> Option { + let mappings = self.gamepads.get(id)?.read().unwrap(); + mappings.buttons.get(code).copied() + } + + pub fn map_axis(&self, id: &GamepadId, code: &Code) -> Option<(VBKey, VBKey)> { + let mappings = self.gamepads.get(id)?.read().unwrap(); + mappings.axes.get(code).copied() + } + + pub fn add_keyboard_mapping(&mut self, key: VBKey, keyboard_key: PhysicalKey) { + let entry = self.keys.entry(keyboard_key).or_insert(VBKey::empty()); + *entry = entry.union(key); + } + + pub fn clear_keyboard_mappings(&mut self, key: VBKey) { + self.keys.retain(|_, keys| { + *keys = keys.difference(key); + *keys != VBKey::empty() + }); + } + + pub fn keyboard_mapping_names(&self) -> HashMap { + let mut results: HashMap> = HashMap::new(); + for (keyboard_key, keys) in &self.keys { + let name = match keyboard_key { + PhysicalKey::Code(code) => format!("{code:?}"), + k => format!("{:?}", k), + }; + for key in keys.iter() { + results.entry(key).or_default().push(name.clone()); + } + } + results + .into_iter() + .map(|(k, mut v)| { + v.sort(); + (k, v.join(", ")) }) .collect() } +} - pub fn bind_key(&mut self, sim_id: SimId, vb: VBKey, key: PhysicalKey) { - let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); - if let Some(old) = vb_bindings.insert(vb, key) { - self.key_bindings.remove(&old); - } - self.key_bindings.insert(key, (sim_id, vb)); - } +#[derive(Clone)] +pub struct MappingProvider { + device_mappings: Arc>>>>, + sim_mappings: HashMap>>, +} - pub fn clear_binding(&mut self, sim_id: SimId, vb: VBKey) { - let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); - if let Some(old) = vb_bindings.remove(&vb) { - self.key_bindings.remove(&old); +impl MappingProvider { + pub fn new() -> Self { + let mut mappings = HashMap::new(); + + let mut p1_mappings = InputMapping::default(); + let p2_mappings = InputMapping::default(); + let mut default_key = |code, key| { + p1_mappings.add_keyboard_mapping(key, PhysicalKey::Code(code)); + }; + default_key(KeyCode::KeyA, VBKey::SEL); + default_key(KeyCode::KeyS, VBKey::STA); + default_key(KeyCode::KeyD, VBKey::B); + default_key(KeyCode::KeyF, VBKey::A); + default_key(KeyCode::KeyE, VBKey::LT); + default_key(KeyCode::KeyR, VBKey::RT); + default_key(KeyCode::KeyI, VBKey::RU); + default_key(KeyCode::KeyJ, VBKey::RL); + default_key(KeyCode::KeyK, VBKey::RD); + default_key(KeyCode::KeyL, VBKey::RR); + default_key(KeyCode::ArrowUp, VBKey::LU); + default_key(KeyCode::ArrowLeft, VBKey::LL); + default_key(KeyCode::ArrowDown, VBKey::LD); + default_key(KeyCode::ArrowRight, VBKey::LR); + + mappings.insert(SimId::Player1, Arc::new(RwLock::new(p1_mappings))); + mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings))); + Self { + device_mappings: Arc::new(RwLock::new(HashMap::new())), + sim_mappings: mappings, } } - pub fn key_event(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { - self.key_bindings.get(&event.physical_key).copied() + pub fn for_sim(&self, sim_id: SimId) -> &Arc> { + self.sim_mappings.get(&sim_id).unwrap() + } + + pub fn map_gamepad(&self, sim_id: SimId, gamepad: &Gamepad) { + let device_id = DeviceId( + gamepad.vendor_id().unwrap_or_default(), + gamepad.product_id().unwrap_or_default(), + ); + let mut lock = self.device_mappings.write().unwrap(); + let gamepad_mapping = match lock.entry(device_id) { + Entry::Occupied(entry) => entry.get().clone(), + Entry::Vacant(entry) => { + let mappings = GamepadMapping::for_gamepad(gamepad); + entry.insert(Arc::new(RwLock::new(mappings))).clone() + } + }; + drop(lock); + self.for_sim(sim_id) + .write() + .unwrap() + .gamepads + .insert(gamepad.id(), gamepad_mapping); } }