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

Input rebinding phase 2 #261

Merged
merged 14 commits into from Jul 21, 2017

Conversation

Projects
None yet
4 participants
@Xaeroxe
Member

Xaeroxe commented Jul 21, 2017

Things done in this PR

  • Use smallvec for InputHandler internals, reducing allocations required.
  • Add functions
    • down_buttons
    • released_buttons
    • down_keys
    • released_keys
    • down_mouse_buttons
    • released_mouse_buttons

These functions will be helpful with assigning bindings in the first place when we just need to generically query for "whatever the user pressed."

  • Add functions
    • axes
    • actions

These functions will be helpful for figuring out what to display on input rebinding screens.

  • Enhance actions API to support multiple bindings per action
  • Move private functions into closures
  • Remove the opaque wrappers over iterators, return slice iterators where possible and in other places return a single ButtonIterator type.
  • Change actions and axes to use string keys instead of integer keys.

Eventually there is going to be a phase 3 to make this interact with the asset system, which I plan on doing as soon as Ron is ready for use.

@Xaeroxe Xaeroxe requested a review from amethyst/engine-devs Jul 21, 2017

Xaeroxe added some commits Jul 21, 2017

src/ecs/resources/input.rs
- pub fn pressed_keys(&self) -> PressedKeys {
- self.current_frame.pressed_keys()
+ /// Returns a vector containing pressed down keys.
+ pub fn pressed_keys(&self) -> &SmallVec<[VirtualKeyCode; 16]> {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Could we just coerce this to a slice?

@torkleyy

torkleyy Jul 21, 2017

Member

Could we just coerce this to a slice?

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

We can and that idea is like 1000x better. Thank you!

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

We can and that idea is like 1000x better. Thank you!

src/ecs/resources/input.rs
}
/// Returns a vector containing mouse buttons released on this frame.
- pub fn released_mouse_buttons(&self) -> &SmallVec<[MouseButton; 8]> {
- &self.released_mouse_buttons
+ pub fn released_mouse_buttons(&self) -> &[MouseButton] {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

I would find it more consistent to return an iterator everywhere or a slice everywhere. Since the latter seems to be impossible, let's go for the former. You can just use slice::Iter here.

@torkleyy

torkleyy Jul 21, 2017

Member

I would find it more consistent to return an iterator everywhere or a slice everywhere. Since the latter seems to be impossible, let's go for the former. You can just use slice::Iter here.

src/ecs/resources/input.rs
- Button::Mouse(*mb)
-}
+ /// Returns an action's bindings.
+ pub fn get_action_bindings(&self, id: i32) -> Option<SmallVec<[Button; 8]>> {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

By convention, getters should not start with get if possible.

@torkleyy

torkleyy Jul 21, 2017

Member

By convention, getters should not start with get if possible.

src/ecs/resources/mod.rs
@@ -11,6 +11,6 @@ mod broadcaster;
pub use self::broadcaster::Broadcaster;
pub use self::camera::{Camera, Projection};
-pub use self::input::{Axis, Button, InputHandler, PressedButtons, PressedKeys, PressedMouseButtons};
+pub use self::input::{Axis, Button, InputHandler, ButtonIterator};

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Please don't forget to order these imports.

@torkleyy

torkleyy Jul 21, 2017

Member

Please don't forget to order these imports.

src/lib.rs
@@ -71,6 +71,7 @@ pub extern crate amethyst_renderer as renderer;
pub extern crate amethyst_config as config;
extern crate cgmath;
+extern crate smallvec;

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Please move it down so the alphabetical order is maintained.

@torkleyy

torkleyy Jul 21, 2017

Member

Please move it down so the alphabetical order is maintained.

@ebkalderon

Apart from some minor nitpicks, the meat of this PR looks solid! Thank you, @Xaeroxe.

src/ecs/resources/input.rs
@@ -333,9 +333,9 @@ impl InputHandler {
}
/// Returns the value of an axis by the i32 id, if the id doesn't exist this returns None.
- pub fn axis_value(&self, id: i32) -> Option<f32> {
+ pub fn axis_value(&self, id: &str) -> Option<f32> {

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Since this is a user-facing API, could we use AsRef<str> instead of &str in all the axis_ methods so users can easily pass in different kinds of string types?

@ebkalderon

ebkalderon Jul 21, 2017

Member

Since this is a user-facing API, could we use AsRef<str> instead of &str in all the axis_ methods so users can easily pass in different kinds of string types?

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

I disagree. It's more difficult to read and you'll almost always pass a literal. If we argue with "it can save the user a method call sometimes", then Rust's choice being explicit about conversions doesn't make sense.

@torkleyy

torkleyy Jul 21, 2017

Member

I disagree. It's more difficult to read and you'll almost always pass a literal. If we argue with "it can save the user a method call sometimes", then Rust's choice being explicit about conversions doesn't make sense.

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

The AsRef trait exists precisely for this reason, though. It's meant to eliminate boilerplate in case users decide to pass in a String, CStr(ing), OsStr(ing), or perhaps even a simple array of bytes, and more importantly, it can prevent extra heap allocations on the user's end.

@ebkalderon

ebkalderon Jul 21, 2017

Member

The AsRef trait exists precisely for this reason, though. It's meant to eliminate boilerplate in case users decide to pass in a String, CStr(ing), OsStr(ing), or perhaps even a simple array of bytes, and more importantly, it can prevent extra heap allocations on the user's end.

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

The last part is only valid for Into<String>, not for AsRef.

@torkleyy

torkleyy Jul 21, 2017

Member

The last part is only valid for Into<String>, not for AsRef.

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Good point, I looked into it and I must have indeed been thinking about Into<String>. But my first point still stands. Even the Rust standard library makes frequent use of AsRef in exactly this way to avoid such boilerplate, and I think that users might appreciate an API that assists them similarly with zero runtime cost.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Good point, I looked into it and I must have indeed been thinking about Into<String>. But my first point still stands. Even the Rust standard library makes frequent use of AsRef in exactly this way to avoid such boilerplate, and I think that users might appreciate an API that assists them similarly with zero runtime cost.

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

If we use Into<String> we'll likely be introducing an unnecessary allocation, as the hashmap prefers to take keys by reference rather than by value. Most usage of this is going to be string literals.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

If we use Into<String> we'll likely be introducing an unnecessary allocation, as the hashmap prefers to take keys by reference rather than by value. Most usage of this is going to be string literals.

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

@Xaeroxe Sorry, perhaps I wasn't clear enough. I am suggesting we use AsRef<str>, not Into<String>. I had mistakenly pointed out that AsRef<str> would reduce unnecessary allocations, confusing it for Into<String>.

However, I still stand by my point that AsRef<str> will behave identically to &str except it will allow users to pass in other string slice types like OsStr and CStr with less boilerplate. I also cite the Rust standard library as a frequent user of this pattern.

@ebkalderon

ebkalderon Jul 21, 2017

Member

@Xaeroxe Sorry, perhaps I wasn't clear enough. I am suggesting we use AsRef<str>, not Into<String>. I had mistakenly pointed out that AsRef<str> would reduce unnecessary allocations, confusing it for Into<String>.

However, I still stand by my point that AsRef<str> will behave identically to &str except it will allow users to pass in other string slice types like OsStr and CStr with less boilerplate. I also cite the Rust standard library as a frequent user of this pattern.

Cargo.toml
@@ -40,6 +40,7 @@ specs = "0.9.1"
rayon = "0.7"
ticketed_lock = "0.1"
wavefront_obj = "5.0"
+smallvec = "0.4"

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Please move this entry upward to retain alphabetical order.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Please move this entry upward to retain alphabetical order.

+ }
+ }
+ None => {
+ make_new = true;

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Why have a make_new variable? Can't lines 469-471 be copied into this branch verbatim and be easier to read?

@ebkalderon

ebkalderon Jul 21, 2017

Member

Why have a make_new variable? Can't lines 469-471 be copied into this branch verbatim and be easier to read?

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

No, because it makes the borrow checker mad. self.actions is already borrowed thanks to the match statement.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

No, because it makes the borrow checker mad. self.actions is already borrowed thanks to the match statement.

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Ahh, I see. I forgot about the borrow checker. I wonder if the upcoming non-lexical lifetimes in Rust will help with these kinds of issues.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Ahh, I see. I forgot about the borrow checker. I wonder if the upcoming non-lexical lifetimes in Rust will help with these kinds of issues.

src/ecs/resources/input.rs
+ action_bindings.swap_remove(index);
+ }
+ if action_bindings.len() == 0 {
+ kill_it = true;

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Same comment as with the make_new variable in insert_action_binding.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Same comment as with the make_new variable in insert_action_binding.

src/ecs/resources/input.rs
- pub fn pressed_keys(&self) -> PressedKeys {
- self.current_frame.pressed_keys()
+ /// Returns a vector containing pressed down keys.
+ pub fn pressed_keys(&self) -> Iter<VirtualKeyCode> {

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could this be made into a type alias called KeyCodes?

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could this be made into a type alias called KeyCodes?

src/ecs/resources/input.rs
- pub fn mouse_button_is_pressed(&self, button: MouseButton) -> bool {
- self.current_frame.mouse_button_is_pressed(button)
+ /// Returns a vector containing pressed down mouse buttons.
+ pub fn pressed_mouse_buttons(&self) -> Iter<MouseButton> {

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could this be made into a type alias called MouseButtons?

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could this be made into a type alias called MouseButtons?

src/ecs/resources/input.rs
+ }
+
+ /// Returns a vector containing the buttons that were pressed this frame
+ pub fn down_buttons(&self) -> ButtonIterator {

This comment has been minimized.

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could ButtonIterator be renamed to Buttons to remain consistent with other iterator names?

@ebkalderon

ebkalderon Jul 21, 2017

Member

Could ButtonIterator be renamed to Buttons to remain consistent with other iterator names?

@Xaeroxe

This comment has been minimized.

Show comment
Hide comment
@Xaeroxe

Xaeroxe Jul 21, 2017

Member

@ebkalderon Changes you requested have been made.

Member

Xaeroxe commented Jul 21, 2017

@ebkalderon Changes you requested have been made.

@torkleyy

That's great! It still needs some polishing, though.

src/ecs/resources/input.rs
- /// Returns an iterator for all the pressed down keys.
- pub fn pressed_keys(&self) -> PressedKeys {
- self.current_frame.pressed_keys()
+ /// Returns a vector containing pressed down keys.

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Doc comment seems outdated.

@torkleyy

torkleyy Jul 21, 2017

Member

Doc comment seems outdated.

src/ecs/resources/input.rs
- /// Checks if the given key is being pressed.
- pub fn key_is_pressed(&self, key: VirtualKeyCode) -> bool {
- self.current_frame.key_is_pressed(key)
+ /// Returns a vector containing keys pressed on this frame.

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Same here

@torkleyy

torkleyy Jul 21, 2017

Member

Same here

src/ecs/resources/input.rs
- /// Checks if all the given keys are being pressed.
- pub fn keys_are_pressed(&self, keys: &[VirtualKeyCode]) -> bool {
- self.current_frame.keys_are_pressed(keys)
+ /// Returns a vector containing keys released on this frame.

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Same here

@torkleyy

torkleyy Jul 21, 2017

Member

Same here

src/ecs/resources/input.rs
+ self.down_mouse_buttons.iter()
+ }
+
+ /// Returns a vector containing mouse buttons released on this frame.

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Same here.

@torkleyy

torkleyy Jul 21, 2017

Member

Same here.

}
/// Checks if all the given actions are pressed.
///
/// If any action in this list is invalid this will return the id of it in Err.
- pub fn actions_are_pressed(&self, actions: &[i32]) -> Result<bool, Vec<i32>> {
+ pub fn actions_are_pressed<T: AsRef<str>>(&self, actions: &[T]) -> Result<bool, Vec<String>> {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Can we remove the verb here like it is the case above with the mouse buttons?

@torkleyy

torkleyy Jul 21, 2017

Member

Can we remove the verb here like it is the case above with the mouse buttons?

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Verb is used to visually distinguish this function name, otherwise the function names get too similar and people get confused.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Verb is used to visually distinguish this function name, otherwise the function names get too similar and people get confused.

src/ecs/resources/input.rs
- /// If one does exist this will replace the action at that id and return it.
- pub fn insert_action(&mut self, id: i32, binding: Button) -> Option<Button> {
- self.actions.insert(id, binding)
+ /// Get's a list of all axes

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

I don't think we need the '.

@torkleyy

torkleyy Jul 21, 2017

Member

I don't think we need the '.

src/ecs/resources/input.rs
- self.actions.insert(id, binding)
+ /// Get's a list of all axes
+ pub fn axes(&self) -> Vec<String> {
+ self.axes.keys().map(|k| k.clone()).collect::<Vec<String>>()

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Why collect it?

@torkleyy

torkleyy Jul 21, 2017

Member

Why collect it?

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Cuz I don't want another opaque Iterator type for this function, and this function is called very infrequently.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Cuz I don't want another opaque Iterator type for this function, and this function is called very infrequently.

+ /// This will insert a new binding between this action and the button.
+ pub fn insert_action_binding<T: AsRef<str>>(&mut self, id: T, binding: Button) {
+ let mut make_new = false;
+ match self.actions.get_mut(id.as_ref()) {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

I think we can use the Entry API here.

@torkleyy

torkleyy Jul 21, 2017

Member

I think we can use the Entry API here.

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

We can, I used it, it mandated an extra allocation in situations where it wasn't necessary, so I took it out.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

We can, I used it, it mandated an extra allocation in situations where it wasn't necessary, so I took it out.

src/ecs/resources/input.rs
+ if let Some(index) = index {
+ action_bindings.swap_remove(index);
+ }
+ if action_bindings.len() == 0 {

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Let's use late binding here: Change the line at the beginning of the method to let kill_it; and change this line to kill_it = action_bindings.is_empty().

@torkleyy

torkleyy Jul 21, 2017

Member

Let's use late binding here: Change the line at the beginning of the method to let kill_it; and change this line to kill_it = action_bindings.is_empty().

This comment has been minimized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Not possible, if the given action isn't valid then we can't run kill_it = action_bindings.is_empty() which leaves kill_it uninitialized.

@Xaeroxe

Xaeroxe Jul 21, 2017

Member

Not possible, if the given action isn't valid then we can't run kill_it = action_bindings.is_empty() which leaves kill_it uninitialized.

src/ecs/resources/mod.rs
@@ -11,6 +11,6 @@ mod broadcaster;
pub use self::broadcaster::Broadcaster;
pub use self::camera::{Camera, Projection};
-pub use self::input::{Axis, Button, InputHandler, PressedButtons, PressedKeys, PressedMouseButtons};
+pub use self::input::{Axis, Button, Buttons, KeyCodes, MouseButtons, InputHandler};

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

Sorry, but this is not ordered alphabetically. Hope I don't annoy you with that.

@torkleyy

torkleyy Jul 21, 2017

Member

Sorry, but this is not ordered alphabetically. Hope I don't annoy you with that.

This comment has been minimized.

@kvark

kvark Jul 21, 2017

Member

Alphabetical ordering together with the maximum line width is super annoying. Gotta redo quite a few lines just to make a simple change :(

@kvark

kvark Jul 21, 2017

Member

Alphabetical ordering together with the maximum line width is super annoying. Gotta redo quite a few lines just to make a simple change :(

This comment has been minimized.

@torkleyy

torkleyy Jul 21, 2017

Member

So what do you suggest? I just think that alphabetical order helps when you read them (and it looks really clean and tidy).

@torkleyy

torkleyy Jul 21, 2017

Member

So what do you suggest? I just think that alphabetical order helps when you read them (and it looks really clean and tidy).

@Xaeroxe

This comment has been minimized.

Show comment
Hide comment
@Xaeroxe

Xaeroxe Jul 21, 2017

Member

@torkleyy Requesting either your approval or additional input

Member

Xaeroxe commented Jul 21, 2017

@torkleyy Requesting either your approval or additional input

@torkleyy

It's great, thanks for addressing my comments!

@torkleyy torkleyy merged commit 87a2846 into amethyst:develop Jul 21, 2017

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@Xaeroxe Xaeroxe deleted the Xaeroxe:input-phase2 branch Aug 8, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment