Skip to content

Commit

Permalink
Refactor for clearer and more performant code
Browse files Browse the repository at this point in the history
  • Loading branch information
Shute052 committed Feb 19, 2024
1 parent fd4415e commit 59b2eec
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 688 deletions.
44 changes: 7 additions & 37 deletions src/action_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,7 @@ impl<A: Actionlike> ActionState<A> {
/// Instead, this is set through [`ActionState::tick()`]
#[inline]
pub fn press(&mut self, action: &A) {
let action_data = match self.action_data_mut(action) {
Some(action_data) => action_data,
None => {
self.set_action_data(action.clone(), ActionData::default());
self.action_data_mut(action).unwrap()
}
};
let action_data = self.action_data.entry(action.clone()).or_default();

// Consumed actions cannot be pressed until they are released
if action_data.consumed {
Expand All @@ -346,13 +340,7 @@ impl<A: Actionlike> ActionState<A> {
/// Instead, this is set through [`ActionState::tick()`]
#[inline]
pub fn release(&mut self, action: &A) {
let action_data = match self.action_data_mut(action) {
Some(action_data) => action_data,
None => {
self.set_action_data(action.clone(), ActionData::default());
self.action_data_mut(action).unwrap()
}
};
let action_data = self.action_data.entry(action.clone()).or_default();

// Once released, consumed actions can be pressed again
action_data.consumed = false;
Expand Down Expand Up @@ -405,13 +393,7 @@ impl<A: Actionlike> ActionState<A> {
/// ```
#[inline]
pub fn consume(&mut self, action: &A) {
let action_data = match self.action_data_mut(action) {
Some(action_data) => action_data,
None => {
self.set_action_data(action.clone(), ActionData::default());
self.action_data_mut(action).unwrap()
}
};
let action_data = self.action_data.entry(action.clone()).or_default();

// This is the only difference from action_state.release(&action)
action_data.consumed = true;
Expand All @@ -438,30 +420,21 @@ impl<A: Actionlike> ActionState<A> {
#[inline]
#[must_use]
pub fn consumed(&self, action: &A) -> bool {
match self.action_data(action) {
Some(action_data) => action_data.consumed,
None => false,
}
matches!(self.action_data(action), Some(action_data) if action_data.consumed)
}

/// Is this `action` currently pressed?
#[inline]
#[must_use]
pub fn pressed(&self, action: &A) -> bool {
match self.action_data(action) {
Some(action_data) => action_data.state.pressed(),
None => false,
}
matches!(self.action_data(action), Some(action_data) if action_data.state.pressed())
}

/// Was this `action` pressed since the last time [tick](ActionState::tick) was called?
#[inline]
#[must_use]
pub fn just_pressed(&self, action: &A) -> bool {
match self.action_data(action) {
Some(action_data) => action_data.state.just_pressed(),
None => false,
}
matches!(self.action_data(action), Some(action_data) if action_data.state.just_pressed())
}

/// Is this `action` currently released?
Expand All @@ -480,10 +453,7 @@ impl<A: Actionlike> ActionState<A> {
#[inline]
#[must_use]
pub fn just_released(&self, action: &A) -> bool {
match self.action_data(action) {
Some(action_data) => action_data.state.just_released(),
None => false,
}
matches!(self.action_data(action), Some(action_data) if action_data.state.just_released())
}

#[must_use]
Expand Down
77 changes: 37 additions & 40 deletions src/axislike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,7 @@ impl DualAxisData {
#[inline]
pub fn direction(&self) -> Option<Direction> {
// 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
Expand All @@ -698,10 +695,7 @@ impl DualAxisData {
#[must_use]
#[inline]
pub fn rotation(&self) -> Option<Rotation> {
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?
Expand Down Expand Up @@ -741,20 +735,20 @@ impl From<DualAxisData> for Vec2 {
/// The shape of the deadzone for a [`DualAxis`] input.
///
/// Input values that are on the boundary of the shape are counted as inside.
/// If a size of a shape is 0.0, then all input values are read, except for 0.0.
/// If the size of a shape is 0.0, then all input values are read, except for 0.0.
///
/// All inputs are scaled to be continuous.
/// So with a ellipse deadzone of a radius of 0.1, the input range `0.1..=1.0` will be scaled to `0.0..=1.0`.
/// So with an ellipse deadzone of a radius of 0.1, the input range `0.1..=1.0` will be scaled to `0.0..=1.0`.
///
/// Deadzone values should be in the range `0.0..=1.0`.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Reflect)]
pub enum DeadZoneShape {
/// Deadzone with the shape of a cross.
///
/// The cross is represented by horizonal and vertical rectangles.
/// Each axis is handled seperately which creates a per-axis "snapping" effect.
/// The cross is represented by horizontal and vertical rectangles.
/// Each axis is handled separately which creates a per-axis "snapping" effect.
Cross {
/// The width of the horizonal axis.
/// The width of the horizontal axis.
///
/// Affects the snapping of the y-axis.
horizontal_width: f32,
Expand Down Expand Up @@ -794,57 +788,60 @@ 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<DualAxisData> {
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)
}
}
}

/// 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<DualAxisData> {
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<DualAxisData> {
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 x_ratio = x / radius_x.max(f32::EPSILON);
let y_ratio = y / radius_y.max(f32::EPSILON);
let is_outside_deadzone = x_ratio.powi(2) + y_ratio.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)
}
}
95 changes: 26 additions & 69 deletions src/clashing_inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ impl<A: Actionlike> InputMap<A> {
pub(crate) fn possible_clashes(&self) -> Vec<Clash<A>> {
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);
}
Expand Down Expand Up @@ -162,11 +162,8 @@ impl<A: Actionlike> InputMap<A> {
}
}

if !clash.inputs_a.is_empty() {
Some(clash)
} else {
None
}
let not_empty = !clash.inputs_a.is_empty();
not_empty.then_some(clash)
}
}

Expand Down Expand Up @@ -196,49 +193,25 @@ impl<A: Actionlike> Clash<A> {
// 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;
}

for button in &[dpad.up, dpad.down, dpad.left, dpad.right] {
if chord.contains(button) {
return true;
}
}

false
chord.len() > 1 && chord.iter().any(|button| dpad_button_clash(dpad, button))
}

fn dpad_button_clash(dpad: &VirtualDPad, button: &InputKind) -> bool {
for dpad_button in &[dpad.up, dpad.down, dpad.left, dpad.right] {
if button == dpad_button {
return true;
}
}

false
[dpad.up, dpad.down, dpad.left, dpad.right]
.iter()
.any(|dpad_button| button == dpad_button)
}

fn dpad_dpad_clash(dpad1: &VirtualDPad, dpad2: &VirtualDPad) -> bool {
for button1 in &[dpad1.up, dpad1.down, dpad1.left, dpad1.right] {
for button2 in &[dpad2.up, dpad2.down, dpad2.left, dpad2.right] {
if button1 == button2 {
return true;
}
}
}

false
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)
}

#[must_use]
Expand All @@ -248,30 +221,23 @@ fn virtual_axis_button_clash(axis: &VirtualAxis, button: &InputKind) -> bool {

#[must_use]
fn virtual_axis_dpad_clash(axis: &VirtualAxis, dpad: &VirtualDPad) -> bool {
for dpad_button in &[dpad.up, dpad.down, dpad.left, dpad.right] {
if dpad_button == &axis.negative || dpad_button == &axis.positive {
return true;
}
}

false
[&dpad.up, &dpad.down, &dpad.left, &dpad.right]
.iter()
.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`?
Expand All @@ -285,17 +251,11 @@ fn chord_chord_clash(chord_a: &Vec<InputKind>, chord_b: &Vec<InputKind>) -> bool
return false;
}

is_subset(chord_a, chord_b) || is_subset(chord_b, chord_a)
}

fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool {
for a in slice_a {
if !slice_b.contains(a) {
return false;
}
fn is_subset(slice_a: &[InputKind], slice_b: &[InputKind]) -> bool {
slice_a.iter().all(|a| slice_b.contains(a))
}

true
is_subset(chord_a, chord_b) || is_subset(chord_b, chord_a)
}

/// Given the `input_streams`, does the provided clash actually occur?
Expand Down Expand Up @@ -325,11 +285,8 @@ fn check_clash<A: Actionlike>(clash: &Clash<A>, input_streams: &InputStreams) ->
}
}

if !clash.inputs_a.is_empty() {
Some(actual_clash)
} else {
None
}
let not_empty = !clash.inputs_a.is_empty();
not_empty.then_some(actual_clash)
}

/// Which (if any) of the actions in the [`Clash`] should be discarded?
Expand Down
Loading

0 comments on commit 59b2eec

Please sign in to comment.