From 282ca735bad0bd2d7574851cb0b7c61f023246c0 Mon Sep 17 00:00:00 2001 From: Hennadii Chernyshchyk Date: Tue, 5 Nov 2024 03:30:48 +0300 Subject: [PATCH] Use `Name` component for gamepad (#16233) # Objective Addressing a suggestion I made in Discord: store gamepad name as a `Name` component. Advantages: - Will be nicely displayed in inspector / editor. - Easier to spawn in tests, just `world.spawn(Gamepad::default())`. ## Solution `Gamepad` component now stores only vendor and product IDs and `Name` stores the gamepad name. Since `GamepadInfo` is no longer necessary, I removed it and merged its fields into the connection event. ## Testing - Run unit tests. --- ## Migration Guide - `GamepadInfo` no longer exists: - Name now accesible via `Name` component. - Other information available on `Gamepad` component directly. - `GamepadConnection::Connected` now stores all info fields directly. --- crates/bevy_gilrs/src/gilrs_system.rs | 37 ++++---- crates/bevy_input/Cargo.toml | 1 + crates/bevy_input/src/gamepad.rs | 120 ++++++++++++++------------ crates/bevy_input/src/lib.rs | 3 +- examples/tools/gamepad_viewer.rs | 4 +- 5 files changed, 87 insertions(+), 78 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index a0ed0e4e76..05f9aa02e9 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -8,7 +8,7 @@ use bevy_ecs::prelude::Commands; use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ - GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent, + GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; @@ -26,15 +26,13 @@ pub fn gilrs_event_startup_system( gamepads.id_to_entity.insert(id, entity); gamepads.entity_to_id.insert(entity, id); - let info = GamepadInfo { - name: gamepad.name().into(), - vendor_id: gamepad.vendor_id(), - product_id: gamepad.product_id(), - }; - events.send(GamepadConnectionEvent { gamepad: entity, - connection: GamepadConnection::Connected(info), + connection: GamepadConnection::Connected { + name: gamepad.name().to_string(), + vendor_id: gamepad.vendor_id(), + product_id: gamepad.product_id(), + }, }); } } @@ -62,20 +60,17 @@ pub fn gilrs_event_system( entity }); - let info = GamepadInfo { - name: pad.name().into(), - vendor_id: pad.vendor_id(), - product_id: pad.product_id(), - }; - - events.send( - GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone())) - .into(), - ); - connection_events.send(GamepadConnectionEvent::new( + let event = GamepadConnectionEvent::new( entity, - GamepadConnection::Connected(info), - )); + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); + + events.send(event.clone().into()); + connection_events.send(event); } EventType::Disconnected => { let gamepad = gamepads diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 3ce1e076d6..5ea757f150 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -21,6 +21,7 @@ serialize = ["serde", "smol_str/serde"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false } +bevy_core = { path = "../bevy_core", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [ "serialize", ] } diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 05171949ec..a356e525b4 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1,6 +1,7 @@ //! The gamepad input functionality. use crate::{Axis, ButtonInput, ButtonState}; +use bevy_core::Name; use bevy_ecs::{ change_detection::DetectChangesMut, component::Component, @@ -148,7 +149,7 @@ impl GamepadConnectionEvent { /// Is the gamepad connected? pub fn connected(&self) -> bool { - matches!(self.connection, GamepadConnection::Connected(_)) + matches!(self.connection, GamepadConnection::Connected { .. }) } /// Is the gamepad disconnected? @@ -318,10 +319,10 @@ pub enum ButtonSettingsError { /// ``` /// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton}; /// # use bevy_ecs::system::Query; +/// # use bevy_core::Name; /// # -/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) { -/// for gamepad in &gamepads { -/// let name = &gamepad.info.name; +/// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) { +/// for (name, gamepad) in &gamepads { /// println!("{name}"); /// /// if gamepad.digital.just_pressed(GamepadButton::North) { @@ -338,31 +339,22 @@ pub enum ButtonSettingsError { #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] #[require(GamepadSettings)] pub struct Gamepad { - /// Metadata. - pub info: GamepadInfo, + /// The USB vendor ID as assigned by the USB-IF, if available. + pub vendor_id: Option, + + /// The USB product ID as assigned by the [vendor], if available. + /// + /// [vendor]: Self::vendor_id + pub product_id: Option, + /// [`ButtonInput`] of [`GamepadButton`] representing their digital state pub digital: ButtonInput, + /// [`Axis`] of [`GamepadButton`] representing their analog state. pub analog: Axis, } impl Gamepad { - /// Creates a gamepad with the given metadata. - pub fn new(info: GamepadInfo) -> Self { - let mut analog = Axis::default(); - for button in GamepadButton::all().iter().copied() { - analog.set(button, 0.0); - } - for axis_type in GamepadAxis::all().iter().copied() { - analog.set(axis_type, 0.0); - } - Self { - info, - analog, - digital: ButtonInput::default(), - } - } - /// Returns the left stick as a [`Vec2`] pub fn left_stick(&self) -> Vec2 { Vec2 { @@ -390,32 +382,23 @@ impl Gamepad { } } -// Note that we don't expose `gilrs::Gamepad::uuid` due to -// https://gitlab.com/gilrs-project/gilrs/-/issues/153. -// -/// Metadata associated with a [`Gamepad`]. -#[derive(Debug, Default, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr( - all(feature = "serialize", feature = "bevy_reflect"), - reflect(Serialize, Deserialize) -)] -pub struct GamepadInfo { - /// The name of the gamepad. - /// - /// This name is generally defined by the OS. - /// - /// For example on Windows the name may be "HID-compliant game controller". - pub name: String, +impl Default for Gamepad { + fn default() -> Self { + let mut analog = Axis::default(); + for button in GamepadButton::all().iter().copied() { + analog.set(button, 0.0); + } + for axis_type in GamepadAxis::all().iter().copied() { + analog.set(axis_type, 0.0); + } - /// The USB vendor ID as assigned by the USB-IF, if available. - pub vendor_id: Option, - - /// The USB product ID as assigned by the [vendor], if available. - /// - /// [vendor]: Self::vendor_id - pub product_id: Option, + Self { + vendor_id: None, + product_id: None, + digital: Default::default(), + analog, + } + } } /// Represents gamepad input types that are mapped in the range [0.0, 1.0]. @@ -1227,12 +1210,23 @@ pub fn gamepad_connection_system( for connection_event in connection_events.read() { let id = connection_event.gamepad; match &connection_event.connection { - GamepadConnection::Connected(info) => { + GamepadConnection::Connected { + name, + vendor_id, + product_id, + } => { let Some(mut gamepad) = commands.get_entity(id) else { warn!("Gamepad {:} removed before handling connection event.", id); continue; }; - gamepad.insert(Gamepad::new(info.clone())); + gamepad.insert(( + Name::new(name.clone()), + Gamepad { + vendor_id: *vendor_id, + product_id: *product_id, + ..Default::default() + }, + )); info!("Gamepad {:?} connected.", id); } GamepadConnection::Disconnected => { @@ -1250,6 +1244,9 @@ pub fn gamepad_connection_system( } } +// Note that we don't expose `gilrs::Gamepad::uuid` due to +// https://gitlab.com/gilrs-project/gilrs/-/issues/153. +// /// The connection status of a gamepad. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -1260,7 +1257,20 @@ pub fn gamepad_connection_system( )] pub enum GamepadConnection { /// The gamepad is connected. - Connected(GamepadInfo), + Connected { + /// The name of the gamepad. + /// + /// This name is generally defined by the OS. + /// + /// For example on Windows the name may be "HID-compliant game controller". + name: String, + + /// The USB vendor ID as assigned by the USB-IF, if available. + vendor_id: Option, + + /// The USB product ID as assigned by the vendor, if available. + product_id: Option, + }, /// The gamepad is disconnected. Disconnected, } @@ -1497,8 +1507,8 @@ mod tests { GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection::{Connected, Disconnected}, - GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings, - RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, + GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent, + RawGamepadButtonChangedEvent, RawGamepadEvent, }; use crate::ButtonState; use bevy_app::{App, PreUpdate}; @@ -1871,7 +1881,11 @@ mod tests { .resource_mut::>() .send(GamepadConnectionEvent::new( gamepad, - Connected(GamepadInfo::default()), + Connected { + name: "Test gamepad".to_string(), + vendor_id: None, + product_id: None, + }, )); gamepad } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index f78b264977..7e0225cf75 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -57,7 +57,7 @@ use gamepad::{ gamepad_connection_system, gamepad_event_processing_system, GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent, - GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent, + GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; @@ -142,7 +142,6 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index ec8b64615c..35d4fd8ab3 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -447,7 +447,7 @@ fn update_axes( fn update_connected( mut connected: EventReader, - gamepads: Query<(Entity, &Gamepad)>, + gamepads: Query<(Entity, &Name), With>, text: Single>, mut writer: TextUiWriter, ) { @@ -458,7 +458,7 @@ fn update_connected( let formatted = gamepads .iter() - .map(|(entity, gamepad)| format!("{} - {}", entity, gamepad.info.name)) + .map(|(entity, name)| format!("{} - {}", entity, name)) .collect::>() .join("\n");