Implement multiplayer #2
|
@ -349,7 +349,10 @@ fn process_gamepad_input(mappings: MappingProvider, proxy: EventLoopProxy<UserEv
|
||||||
let Some(gamepad) = gilrs.connected_gamepad(event.id) else {
|
let Some(gamepad) = gilrs.connected_gamepad(event.id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
mappings.map_gamepad(SimId::Player1, &gamepad);
|
mappings.handle_gamepad_connect(&gamepad);
|
||||||
|
}
|
||||||
|
if event.event == EventType::Disconnected {
|
||||||
|
mappings.handle_gamepad_disconnect(event.id);
|
||||||
}
|
}
|
||||||
if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() {
|
if proxy.send_event(UserEvent::GamepadEvent(event)).is_err() {
|
||||||
// main thread has closed! we done
|
// main thread has closed! we done
|
||||||
|
|
99
src/input.rs
99
src/input.rs
|
@ -11,6 +11,14 @@ use crate::emulator::{SimId, VBKey};
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
struct DeviceId(u16, u16);
|
struct DeviceId(u16, u16);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GamepadInfo {
|
||||||
|
pub id: GamepadId,
|
||||||
|
pub name: String,
|
||||||
|
device_id: DeviceId,
|
||||||
|
pub bound_to: Option<SimId>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GamepadMapping {
|
pub struct GamepadMapping {
|
||||||
buttons: HashMap<Code, VBKey>,
|
buttons: HashMap<Code, VBKey>,
|
||||||
axes: HashMap<Code, (VBKey, VBKey)>,
|
axes: HashMap<Code, (VBKey, VBKey)>,
|
||||||
|
@ -106,6 +114,7 @@ impl InputMapping {
|
||||||
pub struct MappingProvider {
|
pub struct MappingProvider {
|
||||||
device_mappings: Arc<RwLock<HashMap<DeviceId, Arc<RwLock<GamepadMapping>>>>>,
|
device_mappings: Arc<RwLock<HashMap<DeviceId, Arc<RwLock<GamepadMapping>>>>>,
|
||||||
sim_mappings: HashMap<SimId, Arc<RwLock<InputMapping>>>,
|
sim_mappings: HashMap<SimId, Arc<RwLock<InputMapping>>>,
|
||||||
|
gamepad_info: Arc<RwLock<HashMap<GamepadId, GamepadInfo>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MappingProvider {
|
impl MappingProvider {
|
||||||
|
@ -136,6 +145,7 @@ impl MappingProvider {
|
||||||
mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings)));
|
mappings.insert(SimId::Player2, Arc::new(RwLock::new(p2_mappings)));
|
||||||
Self {
|
Self {
|
||||||
device_mappings: Arc::new(RwLock::new(HashMap::new())),
|
device_mappings: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
gamepad_info: Arc::new(RwLock::new(HashMap::new())),
|
||||||
sim_mappings: mappings,
|
sim_mappings: mappings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,24 +154,101 @@ impl MappingProvider {
|
||||||
self.sim_mappings.get(&sim_id).unwrap()
|
self.sim_mappings.get(&sim_id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn map_gamepad(&self, sim_id: SimId, gamepad: &Gamepad) {
|
pub fn handle_gamepad_connect(&self, gamepad: &Gamepad) {
|
||||||
let device_id = DeviceId(
|
let device_id = DeviceId(
|
||||||
gamepad.vendor_id().unwrap_or_default(),
|
gamepad.vendor_id().unwrap_or_default(),
|
||||||
gamepad.product_id().unwrap_or_default(),
|
gamepad.product_id().unwrap_or_default(),
|
||||||
);
|
);
|
||||||
let mut lock = self.device_mappings.write().unwrap();
|
let mut lock = self.device_mappings.write().unwrap();
|
||||||
let gamepad_mapping = match lock.entry(device_id) {
|
let mappings = match lock.entry(device_id) {
|
||||||
Entry::Occupied(entry) => entry.get().clone(),
|
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
let mappings = GamepadMapping::for_gamepad(gamepad);
|
let mappings = GamepadMapping::for_gamepad(gamepad);
|
||||||
entry.insert(Arc::new(RwLock::new(mappings))).clone()
|
entry.insert(Arc::new(RwLock::new(mappings)))
|
||||||
}
|
}
|
||||||
};
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
|
}
|
||||||
|
.clone();
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
let mut lock = self.gamepad_info.write().unwrap();
|
||||||
|
let bound_to = SimId::values()
|
||||||
|
.into_iter()
|
||||||
|
.find(|sim_id| lock.values().all(|info| info.bound_to != Some(*sim_id)));
|
||||||
|
if let Entry::Vacant(entry) = lock.entry(gamepad.id()) {
|
||||||
|
let info = GamepadInfo {
|
||||||
|
id: *entry.key(),
|
||||||
|
name: gamepad.name().to_string(),
|
||||||
|
device_id,
|
||||||
|
bound_to,
|
||||||
|
};
|
||||||
|
entry.insert(info);
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
if let Some(sim_id) = bound_to {
|
||||||
|
self.for_sim(sim_id)
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.gamepads
|
||||||
|
.insert(gamepad.id(), mappings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_gamepad_disconnect(&self, gamepad_id: GamepadId) {
|
||||||
|
let mut lock = self.gamepad_info.write().unwrap();
|
||||||
|
let Some(info) = lock.remove(&gamepad_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(sim_id) = info.bound_to {
|
||||||
|
self.for_sim(sim_id)
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.gamepads
|
||||||
|
.remove(&gamepad_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_gamepad(&self, gamepad_id: GamepadId, sim_id: SimId) {
|
||||||
|
self.unassign_gamepad(gamepad_id);
|
||||||
|
let mut lock = self.gamepad_info.write().unwrap();
|
||||||
|
let Some(info) = lock.get_mut(&gamepad_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
info.bound_to = Some(sim_id);
|
||||||
|
let device_id = info.device_id;
|
||||||
|
drop(lock);
|
||||||
|
let Some(device_mappings) = self
|
||||||
|
.device_mappings
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.get(&device_id)
|
||||||
|
.cloned()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
self.for_sim(sim_id)
|
self.for_sim(sim_id)
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.gamepads
|
.gamepads
|
||||||
.insert(gamepad.id(), gamepad_mapping);
|
.insert(gamepad_id, device_mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unassign_gamepad(&self, gamepad_id: GamepadId) {
|
||||||
|
let mut lock = self.gamepad_info.write().unwrap();
|
||||||
|
let Some(info) = lock.get_mut(&gamepad_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(sim_id) = info.bound_to {
|
||||||
|
let mut sim_mapping = self.for_sim(sim_id).write().unwrap();
|
||||||
|
sim_mapping.gamepads.remove(&gamepad_id);
|
||||||
|
}
|
||||||
|
info.bound_to = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gamepad_info(&self) -> Vec<GamepadInfo> {
|
||||||
|
self.gamepad_info
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.values()
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl InputWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) {
|
fn show_key_bindings(&mut self, ui: &mut Ui, sim_id: SimId) {
|
||||||
let mappings = self.mappings.for_sim(sim_id);
|
let mappings = self.mappings.for_sim(sim_id);
|
||||||
let binding_names = {
|
let binding_names = {
|
||||||
let mapping = mappings.read().unwrap();
|
let mapping = mappings.read().unwrap();
|
||||||
|
@ -86,6 +86,37 @@ impl InputWindow {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_gamepads(&mut self, ui: &mut Ui) {
|
||||||
|
let mut gamepads = self.mappings.gamepad_info();
|
||||||
|
gamepads.sort_by_key(|g| usize::from(g.id));
|
||||||
|
|
||||||
|
if gamepads.is_empty() {
|
||||||
|
ui.label("No gamepads connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index, gamepad) in gamepads.into_iter().enumerate() {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(format!("Gamepad {index}: {}", gamepad.name));
|
||||||
|
let mut bound_to = gamepad.bound_to;
|
||||||
|
let mut rebind = false;
|
||||||
|
rebind |= ui
|
||||||
|
.selectable_value(&mut bound_to, Some(SimId::Player1), "Player 1")
|
||||||
|
.changed();
|
||||||
|
rebind |= ui
|
||||||
|
.selectable_value(&mut bound_to, Some(SimId::Player2), "Player 2")
|
||||||
|
.changed();
|
||||||
|
rebind |= ui.selectable_value(&mut bound_to, None, "Nobody").changed();
|
||||||
|
if rebind {
|
||||||
|
match bound_to {
|
||||||
|
Some(sim_id) => self.mappings.assign_gamepad(gamepad.id, sim_id),
|
||||||
|
None => self.mappings.unassign_gamepad(gamepad.id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppWindow for InputWindow {
|
impl AppWindow for InputWindow {
|
||||||
|
@ -104,12 +135,14 @@ impl AppWindow for InputWindow {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1");
|
ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1");
|
||||||
ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2");
|
ui.selectable_value(&mut self.active_tab, InputTab::Player2, "Player 2");
|
||||||
|
ui.selectable_value(&mut self.active_tab, InputTab::Gamepads, "Gamepads");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
match self.active_tab {
|
match self.active_tab {
|
||||||
InputTab::Player1 => self.render_key_bindings(ui, SimId::Player1),
|
InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1),
|
||||||
InputTab::Player2 => self.render_key_bindings(ui, SimId::Player2),
|
InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2),
|
||||||
|
InputTab::Gamepads => self.show_gamepads(ui),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -130,4 +163,5 @@ impl AppWindow for InputWindow {
|
||||||
enum InputTab {
|
enum InputTab {
|
||||||
Player1,
|
Player1,
|
||||||
Player2,
|
Player2,
|
||||||
|
Gamepads,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue