-
Notifications
You must be signed in to change notification settings - Fork 121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor UserInput
and InputKind
variants into separate implementations
#490
Conversation
If there are any parts of the doc comments where examples should be added, please let me know directly. |
8db90b9
to
9c3697a
Compare
# Conflicts: # src/input_processing/dual_axis/custom.rs # src/input_processing/single_axis/custom.rs
# Conflicts: # examples/action_state_resource.rs # examples/axis_inputs.rs # examples/input_processing.rs # examples/mouse_wheel.rs # examples/virtual_dpad.rs # src/axislike.rs # src/buttonlike.rs # src/input_map.rs # src/input_mocking.rs # src/input_streams.rs # src/user_input.rs # tests/gamepad_axis.rs # tests/mouse_motion.rs # tests/mouse_wheel.rs
# Conflicts: # RELEASES.md # examples/input_processing.rs # src/axislike.rs # tests/gamepad_axis.rs
|
# Conflicts: # RELEASES.md # src/input_mocking.rs
/// | ||
/// [`InputMap::set_gamepad`]: crate::input_map::InputMap::set_gamepad | ||
/// | ||
/// ```rust,ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doc tests for this module are ignored, since they required to add usable testing controllers for each example.
I feel that they should be focusing on describing input behavior
# Conflicts: # RELEASES.md
# Conflicts: # RELEASES.md # src/plugin.rs
# Conflicts: # RELEASES.md # examples/input_processing.rs # src/axislike.rs # src/clashing_inputs.rs # tests/gamepad_axis.rs
@Shute052 I'd like to review and merge this this week: please resolve merge conflicts if you get a chance and let me know if there's anything else that remains to be done. |
/// In most cases, the input simply holds itself. | ||
Simple(Box<dyn UserInput>), | ||
|
||
/// The input consists of multiple independent [`UserInput`]s. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the distinction between Composite and Group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is about handling input conflicts. If I understand correctly, when Input A and Input B conflict, one of them is a proper subset of the other.
Group
is used for Chords and other similar input combinations (like Sequences
in future), and these are made up of one or more inputs.
Composite
is for inputs that are composed of at least two inputs, like VirtualAxis
and VirtualDPad
, which are made up of two and four buttons, respectively.
But maybe it could be changed to return a Vec and just check the lengths and basic inputs of each.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, got it.
pub fn len(&self) -> usize { | ||
match self { | ||
Self::Simple(_) => 1, | ||
Self::Composite(_) => 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite surprising that this is 1. If it's not a bug, it needs a doc comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the old UserInput::len
/// Typically obtained by calling [`UserInput::raw_inputs`](crate::user_input::UserInput::raw_inputs). | ||
#[derive(Default, Debug, Clone, PartialEq)] | ||
#[must_use] | ||
pub struct RawInputs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I don't love this design. Doesn't this just get us back to the same place, where UserInput
can't be reasonably extended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This struct has just been moved out from the original user_input.rs file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be able to replace with a new trait for implementing input mocking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see!
Send + Sync + Debug + DynClone + DynEq + DynHash + Reflect + erased_serde::Serialize | ||
{ | ||
/// Defines the kind of behavior that the input should be. | ||
fn kind(&self) -> InputControlKind; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the better design here is to have three separate traits ButtonlikeUserInput
etc that all rely on a core UserInput
trait that captures shared behavior. I think that's likely to be easier to do in a follow-up though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had previously considered splitting it into different input kinds: Button, Axis, DualAxis, etc.; and there are corresponding data types for each. Then, modify the ActionState API, something like this:
if let ButtonState(state) = action_state.get(ButtonAction) {
let pressed = state.pressed;
let pressure = state.pressure; // Gamepad buttons are ranging from 0 to 1, and others are 0 or 1
}
if let AxisValue(value: f32) = action_state.get(AxisAction) {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay so I've finally had a chance to review this in earnest. Overall, I'm very impressed with the quality of both the engineering and documentation: thank you very very much.
The net effect of this refactor is great: much more flexible and principled, with some of the code that I hated most (InputStream) gone.
I have a small concern, which should be addressed now, and then two larger ones, which may make sense to wait on:
- I would like to avoid breaking the end user API when possible during this: can we keep
UserInput::chord
andUserInput::modified
? - I don't like the
BasicInput
struct. I understand why it's needed for clash handling, but I think it's the wrong design because it means that custom implemented user input type won't get clashes resolved properly (and we're back to hardcoding the set). - The
UserInput
trait is too monolithic, and requires the implementation of methods that don't make sense for all input kinds. I think this should be split into buttonlike/single-axis/dual-axis.
If UserInput is a trait, I'm not sure how to add static methods to it when using its implementations as trait objects. However, I have added some builder methods.
|
Alright, I can get behind that. Let's get this merged and then we can start experimenting on further refinement. |
Adopted! |
Objective
The current
UserInput
andInputKind
enums are overly large and challenging to extend with new input types.Solution
Introduce a new trait called
UserInput
trait.Refactor the
UserInput
andInputKind
variants into separate impls of the newUserInput
trait, each focusing on a single responsibility.TODO
Breacking Changes
Modifier
enum toModifierKey
, avoiding potential naming conflicts with other libraries or parts of your codebase.InputKind
variants for representing user inputs, it now represents the kind of data an input can provide (e.g., button-like input, axis-like input).UserInput
enum in favor of the newUserInput
trait and its impls.With*ProcessingPipelineExt
methods to define your preferred processing steps.MouseScrollAxis::Y
will clash withMouseScrollDirection::UP
andMouseScrollDirection::DOWN
.MouseMove
will clash with all individual axes and all directional events.Changelog
UserInput
trait.UserInput
impls for gamepad input events:UserInput
for Bevy’sGamepadAxisType
-related inputs.GamepadStick
: Continuous or discrete movement events of the left or right gamepad stick along both X and Y axes.GamepadControlAxis
: Continuous or discrete movement events of aGamepadAxisType
.GamepadControlDirection
: Discrete movement direction events of aGamepadAxisType
, treated as a button press.UserInput
for Bevy’sGamepadButtonType
directly.GamepadVirtualAxis
, similar to the oldUserInput::VirtualAxis
using twoGamepadButtonType
s.GamepadVirtualDPad
, similar to the oldUserInput::VirtualDPad
using fourGamepadButtonType
s.UserInput
impls for keyboard inputs:UserInput
for Bevy’sKeyCode
directly.UserInput
forModifierKey
.KeyboardVirtualAxis
, similar to the oldUserInput::VirtualAxis
using twoKeyCode
s.KeyboardVirtualDPad
, similar to the oldUserInput::VirtualDPad
using fourKeyCode
s.UserInput
impls for mouse inputs:UserInput
for movement-related inputs.MouseMove
: Continuous or discrete movement events of the mouse both X and Y axes.MouseMoveAxis
: Continuous or discrete movement events of the mouse on an axis, similar to the oldMouseMotionAxisType
.MouseMoveDirection
: Discrete movement direction events of the mouse on an axis, similar to the oldMouseMotionDirection
.UserInput
for wheel-related inputs.MouseScroll
: Continuous or discrete movement events of the mouse wheel both X and Y axes.MouseScrollAxis
: Continuous or discrete movement events of the mouse wheel on an axis, similar to the oldMouseWheelAxisType
.MouseScrollDirection
: Discrete movement direction events of the mouse wheel on an axis, similar to the oldMouseWheelDirection
.InputChord
for combining multiple inputs, similar to the oldUserInput::Chord
.Migration Guide
SingleAxis
is now:GamepadControlAxis
for gamepad axes.MouseMoveAxis::X
andMouseMoveAxis::Y
for continuous mouse movement.MouseScrollAxis::X
andMouseScrollAxis::Y
for continuous mouse wheel movement.DualAxis
is now:GamepadStick
for gamepad sticks.MouseMove::default()
for continuous mouse movement.MouseScroll::default()
for continuous mouse wheel movement.Modifier
is nowModifierKey
.MouseMotionDirection
is nowMouseMoveDirection
.MouseWheelDirection
is nowMouseScrollDirection
.UserInput::Chord
is nowInputChord
.UserInput::VirtualAxis
is now:GamepadVirtualAxis
for four gamepad buttons.KeyboardVirtualAxis
for four keys.MouseMoveAxis::X.digital()
andMouseMoveAxis::Y.digital()
for discrete mouse movement.MouseScrollAxis::X.digital()
andMouseScrollAxis::Y.digital()
for discrete mouse wheel movement.UserInput::VirtualDPad
is now:GamepadVirtualDPad
for four gamepad buttons.KeyboardVirtualDPad
for four keys.MouseMove::default().digital()
for discrete mouse movement.MouseScroll::default().digital()
for discrete mouse wheel movement.Next Steps
InputStreams
into separate implementations.RawInputs