From ca36a7def4e23b4ff9e6d359461978d12dcd710b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Wed, 21 Oct 2020 11:53:49 -0700 Subject: [PATCH] gamepad: expose raw and filtered gamepad events. --- Cargo.toml | 4 +- crates/bevy_app/src/app_builder.rs | 5 +- crates/bevy_app/src/stage.rs | 7 +- crates/bevy_gilrs/src/gilrs_system.rs | 26 +--- crates/bevy_gilrs/src/lib.rs | 9 +- crates/bevy_input/src/gamepad.rs | 131 +++++++++++------- crates/bevy_input/src/lib.rs | 29 ++-- crates/bevy_scene/src/lib.rs | 2 +- examples/input/gamepad_input.rs | 43 +++--- ...input_event.rs => gamepad_input_events.rs} | 15 +- 10 files changed, 140 insertions(+), 131 deletions(-) rename examples/input/{gamepad_input_event.rs => gamepad_input_events.rs} (70%) diff --git a/Cargo.toml b/Cargo.toml index 7b5781e77e987..f4331cc52a6c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -231,8 +231,8 @@ name = "gamepad_input" path = "examples/input/gamepad_input.rs" [[example]] -name = "gamepad_input_event" -path = "examples/input/gamepad_input_event.rs" +name = "gamepad_input_events" +path = "examples/input/gamepad_input_events.rs" [[example]] name = "touch_input" diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index dfd7aa236a7e7..34390a20e5381 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -174,7 +174,8 @@ impl AppBuilder { .add_startup_stage(startup_stage::STARTUP) .add_startup_stage(startup_stage::POST_STARTUP) .add_stage(stage::FIRST) - .add_stage(stage::EVENT_UPDATE) + .add_stage(stage::PRE_EVENT) + .add_stage(stage::EVENT) .add_stage(stage::PRE_UPDATE) .add_stage(stage::UPDATE) .add_stage(stage::POST_UPDATE) @@ -217,7 +218,7 @@ impl AppBuilder { T: Send + Sync + 'static, { self.add_resource(Events::::default()) - .add_system_to_stage(stage::EVENT_UPDATE, Events::::update_system.system()) + .add_system_to_stage(stage::EVENT, Events::::update_system.system()) } pub fn add_resource(&mut self, resource: T) -> &mut Self diff --git a/crates/bevy_app/src/stage.rs b/crates/bevy_app/src/stage.rs index 85f9f1f1ada2f..1e4fa590528b7 100644 --- a/crates/bevy_app/src/stage.rs +++ b/crates/bevy_app/src/stage.rs @@ -1,8 +1,11 @@ /// Name of app stage that runs before all other app stages pub const FIRST: &str = "first"; -/// Name of app stage that updates events. Generally this should run before UPDATE -pub const EVENT_UPDATE: &str = "event_update"; +/// Name of app stage that runs before EVENT +pub const PRE_EVENT: &str = "pre_events"; + +/// Name of app stage that updates events. Runs before UPDATE +pub const EVENT: &str = "events"; /// Name of app stage responsible for performing setup before an update. Runs before UPDATE. pub const PRE_UPDATE: &str = "pre_update"; diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index f448fdeb2aee8..ef03d7fc75bbe 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,42 +1,30 @@ use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use bevy_app::Events; use bevy_ecs::{Resources, World}; -use bevy_input::prelude::*; +use bevy_input::{gamepad::GamepadEventRaw, prelude::*}; use gilrs::{EventType, Gilrs}; -pub fn gilrs_event_startup_system(_world: &mut World, resources: &mut Resources) { - let gilrs = resources.get_thread_local::().unwrap(); - let mut event = resources.get_mut::>().unwrap(); - event.update(); - for (id, _) in gilrs.gamepads() { - event.send(GamepadEvent( - convert_gamepad_id(id), - GamepadEventType::Connected, - )); - } -} - -pub fn girls_event_system(_world: &mut World, resources: &mut Resources) { +pub fn gilrs_event_system(_world: &mut World, resources: &mut Resources) { let mut gilrs = resources.get_thread_local_mut::().unwrap(); - let mut event = resources.get_mut::>().unwrap(); + let mut event = resources.get_mut::>().unwrap(); event.update(); while let Some(gilrs_event) = gilrs.next_event() { match gilrs_event.event { EventType::Connected => { - event.send(GamepadEvent( + event.send(GamepadEventRaw( convert_gamepad_id(gilrs_event.id), GamepadEventType::Connected, )); } EventType::Disconnected => { - event.send(GamepadEvent( + event.send(GamepadEventRaw( convert_gamepad_id(gilrs_event.id), GamepadEventType::Disconnected, )); } EventType::ButtonChanged(gilrs_button, value, _) => { if let Some(button_type) = convert_button(gilrs_button) { - event.send(GamepadEvent( + event.send(GamepadEventRaw( convert_gamepad_id(gilrs_event.id), GamepadEventType::ButtonChanged(button_type, value), )); @@ -44,7 +32,7 @@ pub fn girls_event_system(_world: &mut World, resources: &mut Resources) { } EventType::AxisChanged(gilrs_axis, value, _) => { if let Some(axis_type) = convert_axis(gilrs_axis) { - event.send(GamepadEvent( + event.send(GamepadEventRaw( convert_gamepad_id(gilrs_event.id), GamepadEventType::AxisChanged(axis_type, value), )); diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 99f803f50fa3f..350814f0d01bd 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -4,7 +4,7 @@ mod gilrs_system; use bevy_app::prelude::*; use bevy_ecs::prelude::*; use gilrs::GilrsBuilder; -use gilrs_system::{gilrs_event_startup_system, girls_event_system}; +use gilrs_system::gilrs_event_system; #[derive(Default)] pub struct GilrsPlugin; @@ -18,8 +18,11 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.add_thread_local_resource(gilrs) - .add_startup_system(gilrs_event_startup_system.thread_local_system()) - .add_system_to_stage(stage::FIRST, girls_event_system.thread_local_system()); + .add_startup_system(gilrs_event_system.thread_local_system()) + .add_system_to_stage( + stage::PRE_EVENT, + gilrs_event_system.thread_local_system(), + ); } Err(err) => log::error!("Failed to start Gilrs. {}", err), } diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index bd416935987c5..59ac6b0f8daeb 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -20,6 +20,10 @@ pub enum GamepadEventType { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct GamepadEvent(pub Gamepad, pub GamepadEventType); +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct GamepadEventRaw(pub Gamepad, pub GamepadEventType); + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub enum GamepadButtonType { @@ -66,51 +70,51 @@ pub enum GamepadAxisType { pub struct GamepadAxis(pub Gamepad, pub GamepadAxisType); #[derive(Default, Debug)] -pub struct GamepadSetting { - pub default_button_setting: ButtonSetting, - pub default_axis_setting: AxisSetting, - pub default_button_axis_setting: ButtonAxisSetting, - pub button_settings: HashMap, - pub axis_settings: HashMap, - pub button_axis_settings: HashMap, +pub struct GamepadSettings { + pub default_button_settings: ButtonSettings, + pub default_axis_settings: AxisSettings, + pub default_button_axis_settings: ButtonAxisSettings, + pub button_settings: HashMap, + pub axis_settings: HashMap, + pub button_axis_settings: HashMap, } -impl GamepadSetting { - pub fn get_button_setting(&self, button: GamepadButton) -> &ButtonSetting { +impl GamepadSettings { + pub fn get_button_settings(&self, button: GamepadButton) -> &ButtonSettings { self.button_settings .get(&button) - .unwrap_or(&self.default_button_setting) + .unwrap_or(&self.default_button_settings) } - pub fn get_axis_setting(&self, axis: GamepadAxis) -> &AxisSetting { + pub fn get_axis_settings(&self, axis: GamepadAxis) -> &AxisSettings { self.axis_settings .get(&axis) - .unwrap_or(&self.default_axis_setting) + .unwrap_or(&self.default_axis_settings) } - pub fn get_button_axis_setting(&self, button: GamepadButton) -> &ButtonAxisSetting { + pub fn get_button_axis_settings(&self, button: GamepadButton) -> &ButtonAxisSettings { self.button_axis_settings .get(&button) - .unwrap_or(&self.default_button_axis_setting) + .unwrap_or(&self.default_button_axis_settings) } } #[derive(Debug, Clone)] -pub struct ButtonSetting { +pub struct ButtonSettings { pub press: f32, pub release: f32, } -impl Default for ButtonSetting { +impl Default for ButtonSettings { fn default() -> Self { - ButtonSetting { + ButtonSettings { press: 0.75, release: 0.65, } } } -impl ButtonSetting { +impl ButtonSettings { fn is_pressed(&self, value: f32) -> bool { value >= self.press } @@ -121,7 +125,7 @@ impl ButtonSetting { } #[derive(Debug, Clone)] -pub struct AxisSetting { +pub struct AxisSettings { pub positive_high: f32, pub positive_low: f32, pub negative_high: f32, @@ -129,9 +133,9 @@ pub struct AxisSetting { pub threshold: f32, } -impl Default for AxisSetting { +impl Default for AxisSettings { fn default() -> Self { - AxisSetting { + AxisSettings { positive_high: 0.95, positive_low: 0.05, negative_high: -0.95, @@ -141,7 +145,7 @@ impl Default for AxisSetting { } } -impl AxisSetting { +impl AxisSettings { fn filter(&self, new_value: f32, old_value: Option) -> f32 { if let Some(old_value) = old_value { if (new_value - old_value).abs() <= self.threshold { @@ -162,15 +166,15 @@ impl AxisSetting { } #[derive(Debug, Clone)] -pub struct ButtonAxisSetting { +pub struct ButtonAxisSettings { pub high: f32, pub low: f32, pub threshold: f32, } -impl Default for ButtonAxisSetting { +impl Default for ButtonAxisSettings { fn default() -> Self { - ButtonAxisSetting { + ButtonAxisSettings { high: 0.95, low: 0.05, threshold: 0.01, @@ -178,7 +182,7 @@ impl Default for ButtonAxisSetting { } } -impl ButtonAxisSetting { +impl ButtonAxisSettings { fn filter(&self, new_value: f32, old_value: Option) -> f32 { if let Some(old_value) = old_value { if (new_value - old_value).abs() <= self.threshold { @@ -195,58 +199,73 @@ impl ButtonAxisSetting { } } -#[derive(Default)] -pub struct GamepadEventState { - gamepad_event_reader: EventReader, -} - +#[allow(clippy::float_cmp)] pub fn gamepad_event_system( - mut state: Local, + mut event_reader: Local>, mut button_input: ResMut>, mut axis: ResMut>, mut button_axis: ResMut>, - events: Res>, - settings: Res, + raw_events: Res>, + mut events: ResMut>, + settings: Res, ) { button_input.update(); - for event in state.gamepad_event_reader.iter(&events) { - let (gamepad, event) = (&event.0, &event.1); + for event in event_reader.iter(&raw_events) { + let (gamepad, event) = (event.0, &event.1); match event { GamepadEventType::Connected => { + events.send(GamepadEvent(gamepad, event.clone())); for button_type in ALL_BUTTON_TYPES.iter() { - let gamepad_button = GamepadButton(*gamepad, *button_type); + let gamepad_button = GamepadButton(gamepad, *button_type); button_input.reset(gamepad_button); button_axis.set(gamepad_button, 0.0); } for axis_type in ALL_AXIS_TYPES.iter() { - axis.set(GamepadAxis(*gamepad, *axis_type), 0.0); + axis.set(GamepadAxis(gamepad, *axis_type), 0.0); } } GamepadEventType::Disconnected => { + events.send(GamepadEvent(gamepad, event.clone())); for button_type in ALL_BUTTON_TYPES.iter() { - let gamepad_button = GamepadButton(*gamepad, *button_type); + let gamepad_button = GamepadButton(gamepad, *button_type); button_input.reset(gamepad_button); button_axis.remove(gamepad_button); } for axis_type in ALL_AXIS_TYPES.iter() { - axis.remove(GamepadAxis(*gamepad, *axis_type)); + axis.remove(GamepadAxis(gamepad, *axis_type)); } } GamepadEventType::AxisChanged(axis_type, value) => { - let gamepad_axis = GamepadAxis(*gamepad, *axis_type); - let value = settings - .get_axis_setting(gamepad_axis) - .filter(*value, axis.get(gamepad_axis)); - axis.set(gamepad_axis, value); + let gamepad_axis = GamepadAxis(gamepad, *axis_type); + let old_value = axis.get(gamepad_axis); + let filtered_value = settings + .get_axis_settings(gamepad_axis) + .filter(*value, old_value); + axis.set(gamepad_axis, filtered_value); + + // only send event if axis has changed after going through filters + if let Some(old_value) = old_value { + if old_value == filtered_value { + return; + } + } else if filtered_value == 0.0 { + return; + } + + events.send(GamepadEvent( + gamepad, + GamepadEventType::AxisChanged(*axis_type, filtered_value), + )) } GamepadEventType::ButtonChanged(button_type, value) => { - let gamepad_button = GamepadButton(*gamepad, *button_type); + let gamepad_button = GamepadButton(gamepad, *button_type); + let old_value = button_axis.get(gamepad_button); let filtered_value = settings - .get_button_axis_setting(gamepad_button) - .filter(*value, button_axis.get(gamepad_button)); + .get_button_axis_settings(gamepad_button) + .filter(*value, old_value); button_axis.set(gamepad_button, filtered_value); - let button_property = settings.get_button_setting(gamepad_button); + let button_property = settings.get_button_settings(gamepad_button); if button_input.pressed(gamepad_button) { if button_property.is_released(*value) { button_input.release(gamepad_button); @@ -254,6 +273,20 @@ pub fn gamepad_event_system( } else if button_property.is_pressed(*value) { button_input.press(gamepad_button); } + + // only send event if axis has changed after going through filters + if let Some(old_value) = old_value { + if old_value == filtered_value { + return; + } + } else if filtered_value == 0.0 { + return; + } + + events.send(GamepadEvent( + gamepad, + GamepadEventType::ButtonChanged(*button_type, filtered_value), + )) } } } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 6c528c2dda28c..d1ff1b75ebc4c 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -27,7 +27,10 @@ use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotio use touch::{touch_screen_input_system, TouchInput, Touches}; use bevy_ecs::IntoQuerySystem; -use gamepad::{gamepad_event_system, GamepadAxis, GamepadButton, GamepadEvent, GamepadSetting}; +use gamepad::{ + gamepad_event_system, GamepadAxis, GamepadButton, GamepadEvent, GamepadEventRaw, + GamepadSettings, +}; /// Adds keyboard and mouse input to an App #[derive(Default)] @@ -40,30 +43,18 @@ impl Plugin for InputPlugin { .add_event::() .add_event::() .init_resource::>() - .add_system_to_stage( - bevy_app::stage::EVENT_UPDATE, - keyboard_input_system.system(), - ) + .add_system_to_stage(bevy_app::stage::EVENT, keyboard_input_system.system()) .init_resource::>() - .add_system_to_stage( - bevy_app::stage::EVENT_UPDATE, - mouse_button_input_system.system(), - ) + .add_system_to_stage(bevy_app::stage::EVENT, mouse_button_input_system.system()) .add_event::() - .init_resource::() + .add_event::() + .init_resource::() .init_resource::>() .init_resource::>() .init_resource::>() - .add_startup_system_to_stage( - bevy_app::startup_stage::POST_STARTUP, - gamepad_event_system.system(), - ) - .add_system_to_stage(bevy_app::stage::EVENT_UPDATE, gamepad_event_system.system()) + .add_system_to_stage(bevy_app::stage::EVENT, gamepad_event_system.system()) .add_event::() .init_resource::() - .add_system_to_stage( - bevy_app::stage::EVENT_UPDATE, - touch_screen_input_system.system(), - ); + .add_system_to_stage(bevy_app::stage::EVENT, touch_screen_input_system.system()); } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 7be0a4e346a44..b850c8a733f3a 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -30,7 +30,7 @@ impl Plugin for ScenePlugin { .add_asset::() .init_asset_loader::() .init_resource::() - .add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE) + .add_stage_after(stage::EVENT, SCENE_STAGE) .add_system_to_stage(SCENE_STAGE, scene_spawner_system.thread_local_system()); } } diff --git a/examples/input/gamepad_input.rs b/examples/input/gamepad_input.rs index 768f90b358211..1b8279489ddac 100644 --- a/examples/input/gamepad_input.rs +++ b/examples/input/gamepad_input.rs @@ -40,32 +40,25 @@ fn gamepad_system( button_axes: Res>, axes: Res>, ) { - for gamepad in lobby.gamepads.iter() { - let south_button = GamepadButton(*gamepad, GamepadButtonType::South); - if button_inputs.just_pressed(south_button) { - println!( - "{:?} of {:?} is just pressed", - GamepadButtonType::South, - gamepad - ); - } else if button_inputs.just_released(south_button) { - println!( - "{:?} of {:?} is just released", - GamepadButtonType::South, - gamepad - ); + for gamepad in lobby.gamepads.iter().cloned() { + if button_inputs.just_pressed(GamepadButton(gamepad, GamepadButtonType::South)) { + println!("{:?} just pressed South", gamepad); + } else if button_inputs.just_released(GamepadButton(gamepad, GamepadButtonType::South)) { + println!("{:?} just released South", gamepad); } - println!( - "For {:?}: {:?} is {:.4}, {:?} is {:.4}", - gamepad, - GamepadButtonType::RightTrigger2, - button_axes - .get(GamepadButton(*gamepad, GamepadButtonType::RightTrigger2)) - .unwrap_or(0.0), - GamepadAxisType::LeftStickX, - axes.get(GamepadAxis(*gamepad, GamepadAxisType::LeftStickX)) - .unwrap_or(0.0) - ) + let right_trigger = button_axes + .get(GamepadButton(gamepad, GamepadButtonType::RightTrigger2)) + .unwrap(); + if right_trigger.abs() > 0.01 { + println!("{:?} RightTrigger2 value is {}", gamepad, right_trigger); + } + + let left_stick_x = axes + .get(GamepadAxis(gamepad, GamepadAxisType::LeftStickX)) + .unwrap(); + if left_stick_x.abs() > 0.01 { + println!("{:?} LeftStickX value is {}", gamepad, left_stick_x); + } } } diff --git a/examples/input/gamepad_input_event.rs b/examples/input/gamepad_input_events.rs similarity index 70% rename from examples/input/gamepad_input_event.rs rename to examples/input/gamepad_input_events.rs index 4052bd62fe55b..5395aec4b6fa1 100644 --- a/examples/input/gamepad_input_event.rs +++ b/examples/input/gamepad_input_events.rs @@ -4,18 +4,15 @@ use bevy_input::gamepad::{GamepadEvent, GamepadEventType}; fn main() { App::build() .add_default_plugins() - .add_startup_system(gamepad_raw_events.system()) - .add_system(gamepad_raw_events.system()) + .add_system(gamepad_events.system()) .run(); } -#[derive(Default)] -struct State { - gamepad_event_reader: EventReader, -} - -fn gamepad_raw_events(mut state: Local, gamepad_event: Res>) { - for event in state.gamepad_event_reader.iter(&gamepad_event) { +fn gamepad_events( + mut event_reader: Local>, + gamepad_event: Res>, +) { + for event in event_reader.iter(&gamepad_event) { match &event { GamepadEvent(gamepad, GamepadEventType::Connected) => { println!("{:?} Connected", gamepad);