Skip to content

Commit

Permalink
Use Name component for gamepad (#16233)
Browse files Browse the repository at this point in the history
# 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.
  • Loading branch information
Shatur authored Nov 5, 2024
1 parent 718688e commit 282ca73
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 78 deletions.
37 changes: 16 additions & 21 deletions crates/bevy_gilrs/src/gilrs_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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(),
},
});
}
}
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_input/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] }
Expand Down
120 changes: 67 additions & 53 deletions crates/bevy_input/src/gamepad.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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) {
Expand All @@ -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<u16>,

/// The USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,

/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub digital: ButtonInput<GamepadButton>,

/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub analog: Axis<GamepadInput>,
}

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 {
Expand Down Expand Up @@ -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,

/// The USB vendor ID as assigned by the USB-IF, if available.
pub vendor_id: Option<u16>,
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 product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,
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].
Expand Down Expand Up @@ -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 => {
Expand All @@ -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))]
Expand All @@ -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<u16>,

/// The USB product ID as assigned by the vendor, if available.
product_id: Option<u16>,
},
/// The gamepad is disconnected.
Disconnected,
}
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -1871,7 +1881,11 @@ mod tests {
.resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new(
gamepad,
Connected(GamepadInfo::default()),
Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
},
));
gamepad
}
Expand Down
3 changes: 1 addition & 2 deletions crates/bevy_input/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -142,7 +142,6 @@ impl Plugin for InputPlugin {
.register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>()
.register_type::<GamepadButtonStateChangedEvent>()
.register_type::<GamepadInfo>()
.register_type::<GamepadConnection>()
.register_type::<GamepadSettings>()
.register_type::<GamepadAxis>()
Expand Down
4 changes: 2 additions & 2 deletions examples/tools/gamepad_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ fn update_axes(

fn update_connected(
mut connected: EventReader<GamepadConnectionEvent>,
gamepads: Query<(Entity, &Gamepad)>,
gamepads: Query<(Entity, &Name), With<Gamepad>>,
text: Single<Entity, With<ConnectedGamepadsText>>,
mut writer: TextUiWriter,
) {
Expand All @@ -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::<Vec<_>>()
.join("\n");

Expand Down

0 comments on commit 282ca73

Please sign in to comment.