From d5f2cb1315fc9bc0f14203976a8f69c2d748ba49 Mon Sep 17 00:00:00 2001 From: Shute052 Date: Sun, 18 Feb 2024 18:55:30 +0800 Subject: [PATCH] Cleanup --- src/action_state.rs | 14 +++--- src/axislike.rs | 67 ++++++++++++------------- src/clashing_inputs.rs | 44 +++++------------ src/display_impl.rs | 10 +--- src/input_map.rs | 16 +++--- src/input_mocking.rs | 108 ++++++++++++++--------------------------- src/input_streams.rs | 36 +++++--------- src/orientation.rs | 5 +- src/systems.rs | 6 +-- src/user_input.rs | 17 ++----- 10 files changed, 117 insertions(+), 206 deletions(-) diff --git a/src/action_state.rs b/src/action_state.rs index dedea529..1d141c92 100644 --- a/src/action_state.rs +++ b/src/action_state.rs @@ -101,7 +101,7 @@ impl ActionState { /// Updates the [`ActionState`] based on a vector of [`ActionData`], ordered by [`Actionlike::id`](Actionlike). /// /// The `action_data` is typically constructed from [`InputMap::which_pressed`](crate::input_map::InputMap), - /// which reads from the assorted [`Input`](bevy::input::ButtonInput) resources. + /// which reads from the assorted [`ButtonInput`](bevy::input::ButtonInput) resources. pub fn update(&mut self, action_data: HashMap) { for (action, action_datum) in action_data { match self.action_data.entry(action) { @@ -263,8 +263,7 @@ impl ActionState { /// Consider clamping this to account for multiple triggering inputs, /// typically using the [`clamped_axis_pair`](Self::clamped_axis_pair) method instead. pub fn axis_pair(&self, action: &A) -> Option { - let action_data = self.action_data(action)?; - action_data.axis_pair + self.action_data(action)?.axis_pair } /// Get the [`DualAxisData`] associated with the corresponding `action`, clamped to `[-1.0, 1.0]`. @@ -443,8 +442,10 @@ impl ActionState { #[inline] #[must_use] pub fn released(&self, action: &A) -> bool { - self.action_data(action) - .map_or(true, |action_data| action_data.state.released()) + match self.action_data(action) { + Some(action_data) => action_data.state.released(), + None => true, + } } /// Was this `action` released since the last time [tick](ActionState::tick) was called? @@ -505,8 +506,7 @@ impl ActionState { /// /// This will also be [`None`] if the action was never pressed or released. pub fn instant_started(&self, action: &A) -> Option { - let action_data = self.action_data(action)?; - action_data.timing.instant_started + self.action_data(action)?.timing.instant_started } /// The [`Duration`] for which the action has been held or released diff --git a/src/axislike.rs b/src/axislike.rs index 2aa5a4eb..5359fb70 100644 --- a/src/axislike.rs +++ b/src/axislike.rs @@ -685,10 +685,7 @@ impl DualAxisData { #[inline] pub fn direction(&self) -> Option { // TODO: replace this quick-n-dirty hack once Direction::new no longer panics - if self.xy.length() > 0.00001 { - return Some(Direction::new(self.xy)); - } - None + (self.xy.length() > 0.00001).then(|| Direction::new(self.xy)) } /// The [`Rotation`] (measured clockwise from midnight) that this axis is pointing towards, if any @@ -697,10 +694,7 @@ impl DualAxisData { #[must_use] #[inline] pub fn rotation(&self) -> Option { - match Rotation::from_xy(self.xy) { - Ok(rotation) => Some(rotation), - Err(_) => None, - } + Rotation::from_xy(self.xy).ok() } /// How far from the origin is this axis's position? @@ -793,15 +787,13 @@ impl std::hash::Hash for DeadZoneShape { impl DeadZoneShape { /// Computes the input value based on the deadzone. pub fn deadzone_input_value(&self, x: f32, y: f32) -> Option { - let value = Vec2::new(x, y); - match self { DeadZoneShape::Cross { horizontal_width, vertical_width, - } => self.cross_deadzone_value(value, *horizontal_width, *vertical_width), + } => self.cross_deadzone_value(x, y, *horizontal_width, *vertical_width), DeadZoneShape::Ellipse { radius_x, radius_y } => { - self.ellipse_deadzone_value(value, *radius_x, *radius_y) + self.ellipse_deadzone_value(x, y, *radius_x, *radius_y) } } } @@ -809,41 +801,46 @@ impl DeadZoneShape { /// Computes the input value based on the cross deadzone. fn cross_deadzone_value( &self, - value: Vec2, + x: f32, + y: f32, horizontal_width: f32, vertical_width: f32, ) -> Option { - let new_x = f32::from(value.x.abs() > vertical_width) * value.x; - let new_y = f32::from(value.y.abs() > horizontal_width) * value.y; - let new_value = Vec2::new(new_x, new_y); - - if new_value == Vec2::ZERO { - None - } else { - let scaled_value = - Self::scale_value(new_value, Vec2::new(vertical_width, horizontal_width)); - Some(DualAxisData::from_xy(scaled_value)) - } + let new_x = deadzone_axis_value(x, vertical_width); + let new_y = deadzone_axis_value(y, horizontal_width); + let is_outside_deadzone = new_x != 0.0 || new_y != 0.0; + is_outside_deadzone.then(|| DualAxisData::new(new_x, new_y)) } /// Computes the input value based on the ellipse deadzone. fn ellipse_deadzone_value( &self, - value: Vec2, + x: f32, + y: f32, radius_x: f32, radius_y: f32, ) -> Option { - let clamped_radius_x = radius_x.max(f32::EPSILON); - let clamped_radius_y = radius_y.max(f32::EPSILON); - if (value.x / clamped_radius_x).powi(2) + (value.y / clamped_radius_y).powi(2) < 1.0 { - return None; - } - - let scaled_value = Self::scale_value(value, Vec2::new(radius_x, radius_y)); - Some(DualAxisData::from_xy(scaled_value)) + let normalized_x = x / radius_x.max(f32::EPSILON); + let normalized_y = y / radius_y.max(f32::EPSILON); + let is_outside_deadzone = normalized_x.powi(2) + normalized_y.powi(2) >= 1.0; + is_outside_deadzone.then(|| { + let new_x = deadzone_axis_value(x, radius_x); + let new_y = deadzone_axis_value(y, radius_y); + DualAxisData::new(new_x, new_y) + }) } +} - fn scale_value(value: Vec2, deadzone_size: Vec2) -> Vec2 { - value.signum() * (value.abs() - deadzone_size).max(Vec2::ZERO) / (1.0 - deadzone_size) +/// Applies the given deadzone to the axis value. +/// +/// Returns 0.0 if the axis value is within the deadzone. +/// Otherwise, returns the normalized axis value between -1.0 and 1.0. +#[inline(always)] +pub(crate) fn deadzone_axis_value(axis_value: f32, deadzone: f32) -> f32 { + let abs_axis_value = axis_value.abs(); + if abs_axis_value <= deadzone { + 0.0 + } else { + axis_value.signum() * (abs_axis_value - deadzone) / (1.0 - deadzone) } } diff --git a/src/clashing_inputs.rs b/src/clashing_inputs.rs index 221c54e5..44256b58 100644 --- a/src/clashing_inputs.rs +++ b/src/clashing_inputs.rs @@ -103,8 +103,8 @@ impl InputMap { pub(crate) fn possible_clashes(&self) -> Vec> { let mut clashes = Vec::default(); - for (action_a, _) in self.iter() { - for (action_b, _) in self.iter() { + for action_a in self.actions() { + for action_b in self.actions() { if let Some(clash) = self.possible_clash(action_a, action_b) { clashes.push(clash); } @@ -162,11 +162,7 @@ impl InputMap { } } - if !clash.inputs_a.is_empty() { - Some(clash) - } else { - None - } + (!clash.inputs_a.is_empty()).then_some(clash) } } @@ -196,22 +192,13 @@ impl Clash { // Does the `button` clash with the `chord`? #[must_use] fn button_chord_clash(button: &InputKind, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - - chord.contains(button) + chord.len() > 1 && chord.contains(button) } // Does the `dpad` clash with the `chord`? #[must_use] fn dpad_chord_clash(dpad: &VirtualDPad, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - [dpad.up, dpad.down, dpad.left, dpad.right] - .iter() - .any(|button| chord.contains(button)) + chord.len() > 1 && chord.iter().any(|button| dpad_button_clash(dpad, button)) } fn dpad_button_clash(dpad: &VirtualDPad, button: &InputKind) -> bool { @@ -223,7 +210,7 @@ fn dpad_button_clash(dpad: &VirtualDPad, button: &InputKind) -> bool { fn dpad_dpad_clash(dpad1: &VirtualDPad, dpad2: &VirtualDPad) -> bool { let iter1 = [&dpad1.up, &dpad1.down, &dpad1.left, &dpad1.right].into_iter(); let iter2 = [&dpad2.up, &dpad2.down, &dpad2.left, &dpad2.right].into_iter(); - iter1.zip(iter2).any(|(left, right)| *left == *right) + iter1.zip(iter2).any(|(left, right)| left == right) } #[must_use] @@ -235,24 +222,21 @@ fn virtual_axis_button_clash(axis: &VirtualAxis, button: &InputKind) -> bool { fn virtual_axis_dpad_clash(axis: &VirtualAxis, dpad: &VirtualDPad) -> bool { [&dpad.up, &dpad.down, &dpad.left, &dpad.right] .iter() - .any(|button| **button == axis.negative || **button == axis.positive) + .any(|button| virtual_axis_button_clash(axis, button)) } #[must_use] fn virtual_axis_chord_clash(axis: &VirtualAxis, chord: &[InputKind]) -> bool { - if chord.len() <= 1 { - return false; - } - - chord.contains(&axis.negative) || chord.contains(&axis.positive) + chord.len() > 1 + && chord + .iter() + .any(|button| virtual_axis_button_clash(axis, button)) } #[must_use] fn virtual_axis_virtual_axis_clash(axis1: &VirtualAxis, axis2: &VirtualAxis) -> bool { - axis1.negative == axis2.negative - || axis1.negative == axis2.positive - || axis1.positive == axis2.negative - || axis1.positive == axis2.positive + virtual_axis_button_clash(axis1, &axis2.negative) + || virtual_axis_button_clash(axis1, &axis2.positive) } /// Does the `chord_a` clash with `chord_b`? @@ -266,8 +250,6 @@ fn chord_chord_clash(chord_a: &Vec, chord_b: &Vec) -> bool return false; } - // Since Chords typically have a few elements, - // making slice-based checks more efficient than set-based ones. fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool { slice_a.iter().all(|a| slice_b.contains(a)) } diff --git a/src/display_impl.rs b/src/display_impl.rs index eb73f0af..1783bbf8 100644 --- a/src/display_impl.rs +++ b/src/display_impl.rs @@ -2,6 +2,7 @@ use crate::axislike::{VirtualAxis, VirtualDPad}; use crate::user_input::{InputKind, UserInput}; +use itertools::Itertools; use std::fmt::Display; impl Display for UserInput { @@ -10,14 +11,7 @@ impl Display for UserInput { // The representation of the button UserInput::Single(button) => write!(f, "{button}"), // The representation of each button, separated by "+" - UserInput::Chord(button_set) => { - let mut string = String::default(); - for button in button_set.iter() { - string.push('+'); - string.push_str(&button.to_string()); - } - write!(f, "{string}") - } + UserInput::Chord(button_set) => f.write_str(&button_set.iter().join("+")), UserInput::VirtualDPad(VirtualDPad { up, down, diff --git a/src/input_map.rs b/src/input_map.rs index 672889c5..208d943e 100644 --- a/src/input_map.rs +++ b/src/input_map.rs @@ -357,6 +357,10 @@ impl InputMap { pub fn iter(&self) -> impl Iterator)> { self.map.iter() } + /// Returns an iterator over actions + pub(crate) fn actions(&self) -> impl Iterator { + self.map.keys() + } /// Returns a reference to the inputs mapped to `action` #[must_use] pub fn get(&self, action: &A) -> Option<&Vec> { @@ -372,11 +376,7 @@ impl InputMap { /// How many input bindings are registered total? #[must_use] pub fn len(&self) -> usize { - let mut i = 0; - for inputs in self.map.values() { - i += inputs.len(); - } - i + self.map.values().map(|inputs| inputs.len()).sum() } /// Are any input bindings registered at all? @@ -406,11 +406,7 @@ impl InputMap { /// Returns `Some(input)` if found. pub fn remove_at(&mut self, action: &A, index: usize) -> Option { let input_vec = self.map.get_mut(action)?; - if input_vec.len() <= index { - None - } else { - Some(input_vec.remove(index)) - } + (input_vec.len() > index).then(|| input_vec.remove(index)) } /// Removes the input for the `action`, if it exists diff --git a/src/input_mocking.rs b/src/input_mocking.rs index 1ad593be..a8a9649c 100644 --- a/src/input_mocking.rs +++ b/src/input_mocking.rs @@ -175,57 +175,36 @@ impl MockInput for MutableInputStreams<'_> { // Discrete mouse wheel events for mouse_wheel_direction in raw_inputs.mouse_wheel.iter() { - match *mouse_wheel_direction { - MouseWheelDirection::Left => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: -1.0, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Right => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 1.0, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Up => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: 1.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelDirection::Down => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: -1.0, - window: Entity::PLACEHOLDER, - }), + let (x, y) = match *mouse_wheel_direction { + MouseWheelDirection::Left => (-1.0, 0.0), + MouseWheelDirection::Right => (1.0, 0.0), + MouseWheelDirection::Up => (0.0, 1.0), + MouseWheelDirection::Down => (0.0, -1.0), }; + self.mouse_wheel.send(MouseWheel { + unit: MouseScrollUnit::Pixel, + x, + y, + window: Entity::PLACEHOLDER, + }); } // Discrete mouse motion event for mouse_motion_direction in raw_inputs.mouse_motion.iter() { - match *mouse_motion_direction { - MouseMotionDirection::Up => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 0.0, y: 1.0 }, - }), - MouseMotionDirection::Down => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 0.0, y: -1.0 }, - }), - MouseMotionDirection::Right => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: 1.0, y: 0.0 }, - }), - MouseMotionDirection::Left => self.mouse_motion.send(MouseMotion { - delta: Vec2 { x: -1.0, y: 0.0 }, - }), + let delta = match *mouse_motion_direction { + MouseMotionDirection::Up => Vec2::new(0.0, 1.0), + MouseMotionDirection::Down => Vec2::new(0.0, -1.0), + MouseMotionDirection::Right => Vec2::new(1.0, 0.0), + MouseMotionDirection::Left => Vec2::new(-1.0, 0.0), }; + self.mouse_motion.send(MouseMotion { delta }); } self.send_gamepad_button_changed(gamepad, &raw_inputs); // Axis data for (outer_axis_type, maybe_position_data) in raw_inputs.axis_data.iter() { - if let Some(position_data) = maybe_position_data { + if let Some(position_data) = *maybe_position_data { match outer_axis_type { AxisType::Gamepad(axis_type) => { if let Some(gamepad) = gamepad { @@ -233,45 +212,30 @@ impl MockInput for MutableInputStreams<'_> { .send(GamepadEvent::Axis(GamepadAxisChangedEvent { gamepad, axis_type: *axis_type, - value: *position_data, + value: position_data, })); } } AxisType::MouseWheel(axis_type) => { - match axis_type { - // FIXME: MouseScrollUnit is not recorded and is always assumed to be Pixel - MouseWheelAxisType::X => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: *position_data, - y: 0.0, - window: Entity::PLACEHOLDER, - }), - MouseWheelAxisType::Y => self.mouse_wheel.send(MouseWheel { - unit: MouseScrollUnit::Pixel, - x: 0.0, - y: *position_data, - window: Entity::PLACEHOLDER, - }), + let (x, y) = match *axis_type { + MouseWheelAxisType::X => (position_data, 0.0), + MouseWheelAxisType::Y => (0.0, position_data), }; + // FIXME: MouseScrollUnit is not recorded and is always assumed to be Pixel + self.mouse_wheel.send(MouseWheel { + unit: MouseScrollUnit::Pixel, + x, + y, + window: Entity::PLACEHOLDER, + }); + } + AxisType::MouseMotion(axis_type) => { + let delta = match *axis_type { + MouseMotionAxisType::X => Vec2::new(position_data, 0.0), + MouseMotionAxisType::Y => Vec2::new(0.0, position_data), + }; + self.mouse_motion.send(MouseMotion { delta }); } - AxisType::MouseMotion(axis_type) => match axis_type { - MouseMotionAxisType::X => { - self.mouse_motion.send(MouseMotion { - delta: Vec2 { - x: *position_data, - y: 0.0, - }, - }); - } - MouseMotionAxisType::Y => { - self.mouse_motion.send(MouseMotion { - delta: Vec2 { - x: 0.0, - y: *position_data, - }, - }); - } - }, } } } diff --git a/src/input_streams.rs b/src/input_streams.rs index cb2386a6..3f999abf 100644 --- a/src/input_streams.rs +++ b/src/input_streams.rs @@ -1,6 +1,6 @@ //! Unified input streams for working with [`bevy::input`] data. -use bevy::ecs::prelude::{Events, ResMut, World}; +use bevy::ecs::prelude::{Event, Events, ResMut, World}; use bevy::ecs::system::SystemState; use bevy::input::{ gamepad::{Gamepad, GamepadAxis, GamepadButton, GamepadEvent, Gamepads}, @@ -9,11 +9,11 @@ use bevy::input::{ Axis, ButtonInput, }; use bevy::math::Vec2; -use bevy::prelude::Event; use bevy::utils::HashSet; use crate::axislike::{ - AxisType, DualAxisData, MouseMotionAxisType, MouseWheelAxisType, SingleAxis, VirtualAxis, + deadzone_axis_value, AxisType, DualAxisData, MouseMotionAxisType, MouseWheelAxisType, + SingleAxis, VirtualAxis, }; use crate::buttonlike::{MouseMotionDirection, MouseWheelDirection}; use crate::prelude::DualAxis; @@ -106,21 +106,16 @@ impl<'a> InputStreams<'a> { value < axis.negative_low || value > axis.positive_low } - InputKind::GamepadButton(button_type) => { - if let Some(gamepad) = self.associated_gamepad { + InputKind::GamepadButton(button_type) => self + .associated_gamepad + .into_iter() + .chain(self.gamepads.iter()) + .any(|gamepad| { self.gamepad_buttons.pressed(GamepadButton { gamepad, button_type, }) - } else { - self.gamepads.iter().any(|gamepad| { - self.gamepad_buttons.pressed(GamepadButton { - gamepad, - button_type, - }) - }) - } - } + }), InputKind::PhysicalKey(keycode) => { matches!(self.keycodes, Some(keycodes) if keycodes.pressed(keycode)) } @@ -185,13 +180,7 @@ impl<'a> InputStreams<'a> { /// If you need to ensure that this value is always in the range `[-1., 1.]`, /// be sure to clamp the returned data. pub fn input_value(&self, input: &UserInput, include_deadzone: bool) -> f32 { - let use_button_value = || -> f32 { - if self.input_pressed(input) { - 1.0 - } else { - 0.0 - } - }; + let use_button_value = || -> f32 { f32::from(self.input_pressed(input)) }; // Helper that takes the value returned by an axis and returns 0.0 if it is not within the // triggering range. @@ -201,12 +190,12 @@ impl<'a> InputStreams<'a> { return 0.0; } - let width = if value.is_sign_positive() { + let deadzone = if value.is_sign_positive() { axis.positive_low.abs() } else { axis.negative_low.abs() }; - value = value.signum() * (value.abs() - width).max(0.0) / (1.0 - width); + value = deadzone_axis_value(value, deadzone); } if axis.inverted { value *= -1.0; @@ -364,6 +353,7 @@ impl<'a> InputStreams<'a> { fn extract_single_axis_data(&self, positive: &InputKind, negative: &InputKind) -> f32 { let positive = self.input_value(&UserInput::Single(*positive), true); let negative = self.input_value(&UserInput::Single(*negative), true); + positive.abs() - negative.abs() } diff --git a/src/orientation.rs b/src/orientation.rs index d7cb28f5..8c2aaf39 100644 --- a/src/orientation.rs +++ b/src/orientation.rs @@ -651,8 +651,9 @@ mod conversions { // for 1.0 and -1.0 can't be represented exactly, so our unit vectors start with an // approximate value and both `atan2` above and `from_radians` below magnify the // imprecision. So, we cheat. - const APPROX_SOUTH: f32 = -std::f32::consts::FRAC_PI_2; - const APPROX_NORTHWEST: f32 = 1.5 * std::f32::consts::FRAC_PI_2; + use std::f32::consts::FRAC_PI_2; + const APPROX_SOUTH: f32 = -FRAC_PI_2; + const APPROX_NORTHWEST: f32 = 1.5 * FRAC_PI_2; if radians == APPROX_NORTHWEST { Rotation::from_degrees_int(135) } else if radians == APPROX_SOUTH { diff --git a/src/systems.rs b/src/systems.rs index cbd71ee1..900c7e64 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,8 +1,5 @@ //! The systems that power each [`InputManagerPlugin`](crate::plugin::InputManagerPlugin). -#[cfg(feature = "egui")] -use std::ops::Not; - #[cfg(feature = "ui")] use crate::action_driver::ActionStateDriver; use crate::{ @@ -108,8 +105,7 @@ pub fn update_action_state( #[cfg(feature = "egui")] let keycodes = maybe_egui .iter_mut() - .any(|(_, mut ctx)| ctx.get_mut().wants_keyboard_input()) - .not() + .all(|(_, mut ctx)| !ctx.get_mut().wants_keyboard_input()) .then_some(keycodes) .flatten(); diff --git a/src/user_input.rs b/src/user_input.rs index 5b05b292..d1582ee2 100644 --- a/src/user_input.rs +++ b/src/user_input.rs @@ -48,17 +48,10 @@ impl UserInput { /// /// If `inputs` has a length of 1, a [`UserInput::Single`] variant will be returned instead. pub fn chord(inputs: impl IntoIterator>) -> Self { - // We can't just check the length unless we add an ExactSizeIterator bound :( - let mut length: u8 = 0; + let vec: Vec = inputs.into_iter().map(|input| input.into()).collect(); - let mut vec: Vec = Vec::default(); - for button in inputs { - length += 1; - vec.push(button.into()); - } - - match length { - 1 => UserInput::Single(*vec.first().unwrap()), + match vec.len() { + 1 => UserInput::Single(vec[0]), _ => UserInput::Chord(vec), } } @@ -361,9 +354,7 @@ impl RawInputs { InputKind::GamepadButton(button) => self.gamepad_buttons.push(button), InputKind::PhysicalKey(key_code) => self.keycodes.push(key_code), InputKind::Modifier(modifier) => { - let key_codes = modifier.key_codes(); - self.keycodes.push(key_codes[0]); - self.keycodes.push(key_codes[1]); + self.keycodes.extend_from_slice(&modifier.key_codes()); } InputKind::Mouse(button) => self.mouse_buttons.push(button), InputKind::MouseWheel(button) => self.mouse_wheel.push(button),