From b44a95fa51ddee08750af13890fb1fc8e4f1f729 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 5 Oct 2024 18:26:34 -0700 Subject: [PATCH 1/2] Add some missing features from the gamepads-as-entities change that were needed to update `leafwing-input-manager`. The gamepads-as-entities change caused several regressions. This patch fixes each of them: 1. After that PR, there was no stable unique identifier available for gamepads. To fix this, this PR introduces several new fields on `GamepadInfo`: `uuid`, `vendor_id`, and `product_id`, as well as associated methods. These fields are simply mirrored from the `gilrs` library. The UUID is the preferred way to identify gamepads across app invocations. 2. That PR removed the methods that allowed iterating over all pressed and released buttons, as well as the method that allowed iterating over the axis values. (It was still technically possible to do so by using reflection to access the private fields of `Gamepad`.) 3. The `Gamepad` component wasn't marked reflectable. This PR fixes that problem. These changes allowed me to forward port `leafwing-input-manager`. --- crates/bevy_gilrs/Cargo.toml | 1 + crates/bevy_gilrs/src/gilrs_system.rs | 7 +++ crates/bevy_input/Cargo.toml | 1 + crates/bevy_input/src/axis.rs | 9 ++++ crates/bevy_input/src/gamepad.rs | 78 ++++++++++++++++++++++++++- crates/bevy_input/src/lib.rs | 3 ++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index b2b037e19edda..33439d9a1d5fe 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -19,6 +19,7 @@ bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } # other gilrs = "0.11.0" thiserror = "1.0" +uuid = "1" [lints] workspace = true diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 4ea9d2bf78790..36013872f5a9d 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -12,6 +12,7 @@ use bevy_input::gamepad::{ RawGamepadButtonChangedEvent, RawGamepadEvent, }; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; +use uuid::Uuid; pub fn gilrs_event_startup_system( mut commands: Commands, @@ -28,6 +29,9 @@ pub fn gilrs_event_startup_system( let info = GamepadInfo { name: gamepad.name().into(), + uuid: Uuid::from_bytes(gamepad.uuid()), + vendor_id: gamepad.vendor_id(), + product_id: gamepad.product_id(), }; events.send(GamepadConnectionEvent { @@ -62,6 +66,9 @@ pub fn gilrs_event_system( let info = GamepadInfo { name: pad.name().into(), + uuid: Uuid::from_bytes(pad.uuid()), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), }; events.send( diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 9abf8db2999a3..9197190ebb65c 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -38,6 +38,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0" smol_str = "0.2" +uuid = "1" [lints] workspace = true diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index df16c0babf76c..072a3d3d60346 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -4,12 +4,16 @@ use bevy_ecs::system::Resource; use bevy_utils::HashMap; use core::hash::Hash; +#[cfg(feature = "bevy_reflect")] +use bevy_reflect::Reflect; + /// Stores the position data of the input devices of type `T`. /// /// The values are stored as `f32`s, using [`Axis::set`]. /// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`] /// inclusive, or unclamped using [`Axis::get_unclamped`]. #[derive(Debug, Resource)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] pub struct Axis { /// The position data of the input devices. axis_data: HashMap, @@ -70,6 +74,11 @@ where pub fn remove(&mut self, input_device: T) -> Option { self.axis_data.remove(&input_device) } + + /// Returns an iterator over all axes. + pub fn all_axes(&self) -> impl Iterator { + self.axis_data.keys() + } } #[cfg(test)] diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 152efe74c0ea7..72732e4504bd7 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -18,6 +18,7 @@ use bevy_utils::{ Duration, HashMap, }; use thiserror::Error; +use uuid::Uuid; /// A gamepad event. /// @@ -364,6 +365,7 @@ pub enum ButtonSettingsError { /// } /// ``` #[derive(Component, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))] #[require(GamepadSettings)] pub struct Gamepad { info: GamepadInfo, @@ -374,8 +376,11 @@ pub struct Gamepad { } impl Gamepad { - /// Creates a gamepad with the given metadata - fn new(info: GamepadInfo) -> Self { + /// Creates a gamepad with the given metadata. + /// + /// Ordinarily, you shouldn't call this, but it can be useful for test + /// mocking. + pub fn new(info: GamepadInfo) -> Self { let mut analog = Axis::default(); for button in GamepadButton::all().iter().copied() { analog.set(button.into(), 0.0); @@ -399,6 +404,27 @@ impl Gamepad { self.info.name.as_str() } + /// The UUID of the game pad. + /// + /// Bevy will try to reuse IDs for game controllers across invocations of + /// the app. Therefore, this ID can be used to track the gamepad across + /// runs. + pub fn uuid(&self) -> &Uuid { + &self.info.uuid + } + + /// Returns the USB vendor ID as assigned by the USB-IF, if available. + pub fn vendor_id(&self) -> Option { + self.info.vendor_id + } + + /// Returns the USB product ID as assigned by the [vendor], if available. + /// + /// [vendor]: Self::vendor_id + pub fn product_id(&self) -> Option { + self.info.product_id + } + /// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`]. /// /// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]]. @@ -505,6 +531,34 @@ impl Gamepad { .into_iter() .all(|button_type| self.just_released(button_type)) } + + /// Returns an iterator over all digital [button]s that are pressed. + /// + /// [button]: GamepadButton + pub fn get_pressed(&self) -> impl Iterator { + self.digital.get_pressed() + } + + /// Returns an iterator over all digital [button]s that were just pressed. + /// + /// [button]: GamepadButton + pub fn get_just_pressed(&self) -> impl Iterator { + self.digital.get_just_pressed() + } + + /// Returns an iterator over all digital [button]s that were just released. + /// + /// [button]: GamepadButton + pub fn get_just_released(&self) -> impl Iterator { + self.digital.get_just_released() + } + + /// Returns an iterator over all analog [axes]. + /// + /// [axes]: GamepadInput + pub fn get_analog_axes(&self) -> impl Iterator { + self.analog.all_axes() + } } /// Metadata associated with a [`Gamepad`]. @@ -522,6 +576,21 @@ pub struct GamepadInfo { /// /// For example on Windows the name may be "HID-compliant game controller". pub name: String, + + /// The UUID of the game pad. + /// + /// Bevy will try to reuse IDs for game controllers across invocations of + /// the app. Therefore, this ID can be used to track the gamepad across + /// runs. + pub uuid: Uuid, + + /// 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, } /// Represents gamepad input types that are mapped in the range [0.0, 1.0]. @@ -665,6 +734,7 @@ impl GamepadAxis { /// Encapsulation over [`GamepadAxis`] and [`GamepadButton`] // This is done so Gamepad can share a single Axis and simplifies the API by having only one get/get_unclamped method #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] pub enum GamepadInput { /// A [`GamepadAxis`] Axis(GamepadAxis), @@ -1620,6 +1690,7 @@ mod tests { use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; use bevy_ecs::schedule::IntoSystemConfigs; + use uuid::Uuid; fn test_button_axis_settings_filter( settings: ButtonAxisSettings, @@ -1988,6 +2059,9 @@ mod tests { gamepad, Connected(GamepadInfo { name: String::from("Gamepad test"), + uuid: Uuid::parse_str("6d860618-c538-4d51-b0a5-0959b9f8c670").unwrap(), + vendor_id: None, + product_id: None, }), )); gamepad diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index ddcfa5bf17e89..678f488efe6ea 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -51,6 +51,8 @@ use mouse::{ }; use touch::{touch_screen_input_system, TouchInput, Touches}; +#[cfg(feature = "bevy_reflect")] +use gamepad::Gamepad; use gamepad::{ gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection, @@ -134,6 +136,7 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() From 28815474d8af1bed91c8d5c24bba3de6cd9fa33b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 7 Oct 2024 21:04:37 -0700 Subject: [PATCH 2/2] Address review comments --- crates/bevy_gilrs/Cargo.toml | 1 - crates/bevy_gilrs/src/gilrs_system.rs | 3 --- crates/bevy_input/Cargo.toml | 1 - crates/bevy_input/src/axis.rs | 5 +++++ crates/bevy_input/src/gamepad.rs | 27 ++++----------------------- crates/bevy_input/src/lib.rs | 12 ++++++++---- crates/bevy_math/src/curve/easing.rs | 2 +- examples/math/cubic_splines.rs | 2 +- 8 files changed, 19 insertions(+), 34 deletions(-) diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 33439d9a1d5fe..b2b037e19edda 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -19,7 +19,6 @@ bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } # other gilrs = "0.11.0" thiserror = "1.0" -uuid = "1" [lints] workspace = true diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 36013872f5a9d..a0ed0e4e7659e 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -12,7 +12,6 @@ use bevy_input::gamepad::{ RawGamepadButtonChangedEvent, RawGamepadEvent, }; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; -use uuid::Uuid; pub fn gilrs_event_startup_system( mut commands: Commands, @@ -29,7 +28,6 @@ pub fn gilrs_event_startup_system( let info = GamepadInfo { name: gamepad.name().into(), - uuid: Uuid::from_bytes(gamepad.uuid()), vendor_id: gamepad.vendor_id(), product_id: gamepad.product_id(), }; @@ -66,7 +64,6 @@ pub fn gilrs_event_system( let info = GamepadInfo { name: pad.name().into(), - uuid: Uuid::from_bytes(pad.uuid()), vendor_id: pad.vendor_id(), product_id: pad.product_id(), }; diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 9197190ebb65c..9abf8db2999a3 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -38,7 +38,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ serde = { version = "1", features = ["derive"], optional = true } thiserror = "1.0" smol_str = "0.2" -uuid = "1" [lints] workspace = true diff --git a/crates/bevy_input/src/axis.rs b/crates/bevy_input/src/axis.rs index 072a3d3d60346..f2e97777cb45e 100644 --- a/crates/bevy_input/src/axis.rs +++ b/crates/bevy_input/src/axis.rs @@ -79,6 +79,11 @@ where pub fn all_axes(&self) -> impl Iterator { self.axis_data.keys() } + + /// Returns an iterator over all axes and their values. + pub fn all_axes_and_values(&self) -> impl Iterator { + self.axis_data.iter().map(|(axis, value)| (axis, *value)) + } } #[cfg(test)] diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 72732e4504bd7..1e508a1e8f67d 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -18,7 +18,6 @@ use bevy_utils::{ Duration, HashMap, }; use thiserror::Error; -use uuid::Uuid; /// A gamepad event. /// @@ -377,10 +376,7 @@ pub struct Gamepad { impl Gamepad { /// Creates a gamepad with the given metadata. - /// - /// Ordinarily, you shouldn't call this, but it can be useful for test - /// mocking. - pub fn new(info: GamepadInfo) -> Self { + fn new(info: GamepadInfo) -> Self { let mut analog = Axis::default(); for button in GamepadButton::all().iter().copied() { analog.set(button.into(), 0.0); @@ -404,15 +400,6 @@ impl Gamepad { self.info.name.as_str() } - /// The UUID of the game pad. - /// - /// Bevy will try to reuse IDs for game controllers across invocations of - /// the app. Therefore, this ID can be used to track the gamepad across - /// runs. - pub fn uuid(&self) -> &Uuid { - &self.info.uuid - } - /// Returns the USB vendor ID as assigned by the USB-IF, if available. pub fn vendor_id(&self) -> Option { self.info.vendor_id @@ -561,6 +548,9 @@ 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, Clone, PartialEq, Eq)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))] @@ -577,13 +567,6 @@ pub struct GamepadInfo { /// For example on Windows the name may be "HID-compliant game controller". pub name: String, - /// The UUID of the game pad. - /// - /// Bevy will try to reuse IDs for game controllers across invocations of - /// the app. Therefore, this ID can be used to track the gamepad across - /// runs. - pub uuid: Uuid, - /// The USB vendor ID as assigned by the USB-IF, if available. pub vendor_id: Option, @@ -1690,7 +1673,6 @@ mod tests { use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; use bevy_ecs::schedule::IntoSystemConfigs; - use uuid::Uuid; fn test_button_axis_settings_filter( settings: ButtonAxisSettings, @@ -2059,7 +2041,6 @@ mod tests { gamepad, Connected(GamepadInfo { name: String::from("Gamepad test"), - uuid: Uuid::parse_str("6d860618-c538-4d51-b0a5-0959b9f8c670").unwrap(), vendor_id: None, product_id: None, }), diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 678f488efe6ea..f78b264977f5c 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -54,10 +54,11 @@ use touch::{touch_screen_input_system, TouchInput, Touches}; #[cfg(feature = "bevy_reflect")] use gamepad::Gamepad; use gamepad::{ - gamepad_connection_system, gamepad_event_processing_system, GamepadAxisChangedEvent, - GamepadButtonChangedEvent, GamepadButtonStateChangedEvent, GamepadConnection, - GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadRumbleRequest, GamepadSettings, - RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, + gamepad_connection_system, gamepad_event_processing_system, GamepadAxis, + GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent, + GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent, + GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent, + RawGamepadButtonChangedEvent, RawGamepadEvent, }; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] @@ -144,6 +145,9 @@ impl Plugin for InputPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::(); } diff --git a/crates/bevy_math/src/curve/easing.rs b/crates/bevy_math/src/curve/easing.rs index fd163291d20c2..d9e885faea1a6 100644 --- a/crates/bevy_math/src/curve/easing.rs +++ b/crates/bevy_math/src/curve/easing.rs @@ -207,7 +207,7 @@ where /// A [`Curve`] mapping the [unit interval] to itself. /// -/// This leads to a cruve with sudden jumps at the step points and segments with constant values +/// This leads to a curve with sudden jumps at the step points and segments with constant values /// everywhere else. /// /// It uses the function `f(n,t) = round(t * n) / n` diff --git a/examples/math/cubic_splines.rs b/examples/math/cubic_splines.rs index 6a98298ac250d..441ff3d044722 100644 --- a/examples/math/cubic_splines.rs +++ b/examples/math/cubic_splines.rs @@ -127,7 +127,7 @@ impl std::fmt::Display for SplineMode { } /// The current cycling mode, which determines whether the control points should be interpolated -/// cylically (to make a loop). +/// cyclically (to make a loop). #[derive(Clone, Copy, Resource, Default)] enum CyclingMode { #[default]