#leafwing-input-manager

1 messages Β· Page 3 of 1

midnight bramble
#

all sequences need to check input simultaneously until they cancel...

candid vigil
#

AtLeast, AtMost, Range

midnight bramble
#

just unify into range(min,max)

candid vigil
#

just enum variants help users to use

midnight bramble
#

at least is (least, ?)
at most is (0, most)
you do want a timeout on all input sequences to avoid weird behaviours

candid vigil
#

you won't like write u32::MAX everywhere

midnight bramble
#

you won't want max anywhere

#

there's also other conditions combos might break, like getting stunned or running out of resource

candid vigil
#

I personally think using AtLeast(200ms) is more straightforward and easier than Range(200ms, ?)

#

in this situation, you don't really need to worry about what the max might be, so it just makes things simpler

candid vigil
midnight bramble
#

might be more complex than that, i'll need to think about it...

#

short sequence that doesn't allow anything else is easy enough, but everything else seems to spawn more issues

#

and in fighters the sequence doesn't cancel at least move and block...

candid vigil
#

So, if a chord or sequence includes a button that's also another single input, that single input will be canceled out.

midnight bramble
#

thinking about warframe melee combo system, there's also the issue that sequence itself might need chords

#

but warframe has an actual action in between

candid vigil
#

so it should be easy to avoid cancel

midnight bramble
#

each single input is a separate move, but it's a more realistic use case for combos in general

candid vigil
#

so, that won't be a problem

midnight bramble
#

the main problem is the stuff in between

#

animations, resource costs, chance of getting stunned/interrupted, and actually calling the right move

#

so maybe sequence is overkill in general

candid vigil
#

so, ActionState really needs a cancel function

midnight bramble
#

in warframe there's also the same case:
forward doesn't stop movement, block doesn't stop block, so (W+RMB)+E still moves and blocks until you press E

#

then the animation plays and prevents both move and block

#

we probably need someone that's actually working on a fighter to chip in

candid vigil
#

LWIM aims to be an input-action bindings manager, just a wrapper for Bevy's Input, not the game engine itself

#

so this might be beyond its scope

midnight bramble
#

yeah

#

chords handle a huge majority of use cases, the rest might be easier to program manually

#

i'd really like the combo menu to be somehow able to display the correct sequence of real input without any chance of bugs though

#

a display for input stream could be useful in general too

candid vigil
#

the library can strive to meet most of the user's needs, but it's impossible to cover everything

midnight bramble
#

yeah i don't think it should

candid vigil
#

just trying to make everything more useful and easier for everyone

candid vigil
midnight bramble
#

this thing on the left

candid vigil
#

ahhh, Bevy can definitely handle that. LWIM is more like an ActionManager

midnight bramble
#

yep, it's just useful for debugging so i think it definitely should be built-in (or at least have a plugin in examples), alongside the rebind menu

candid vigil
#

see Bevy::input::gamepad

#

and you have ButtonInput<GamepadButton>, which collected all the pressing buttons in a frame

#
/// A "press-able" input of type `T`.
///
/// ## Usage
///
/// This type can be used as a resource to keep the current state of an input, by reacting to
/// events from the input. For a given input value:
///
/// * [`ButtonInput::pressed`] will return `true` between a press and a release event.
/// * [`ButtonInput::just_pressed`] will return `true` for one frame after a press event.
/// * [`ButtonInput::just_released`] will return `true` for one frame after a release event.
#[derive(Debug, Clone, Resource, Reflect)]
#[reflect(Default)]
pub struct ButtonInput<T: Copy + Eq + Hash + Send + Sync + 'static> {
    /// A collection of every button that is currently being pressed.
    pressed: HashSet<T>,
    /// A collection of every button that has just been pressed.
    just_pressed: HashSet<T>,
    /// A collection of every button that has just been released.
    just_released: HashSet<T>,
}
#

It's a resource, so you can use it directly in your system

#
fn button_stream(buttons: Res<ButtonInput<GamepadButton>>) {
  let being_pressed_down_buttons = buttons.get_just_pressed();
}
#

all that's left is to convert them into the corresponding arrows and display them on the screen

midnight bramble
#

but it's not buttons being pressed, otherwise it'd just show controller stick

candid vigil
#

Bevy is an ECS-based game engine, your systems (functions) will run every single frame

#

you could collect the buttons into your collections, and convert the collection into arrows on the screen

midnight bramble
#

so it needs to convert dpad into input only if it's different, or does it already do that?

candid vigil
#

ahh, why not use Stack like collection to handle it?

#

or queue

#

just check the recent button with the newly pressing buttons?

midnight bramble
#

stick isn't a button though

slate zealot
#

oh Shute052! I love your work on the LWIM repo!

candid vigil
#

stick can be acting as a button in Bevy

#

or LWIM?

midnight bramble
#

stick needs to act as a dpad, and change in direction acting as input started

candid vigil
#

IIRC, Bevy has an event like axis value changed

midnight bramble
#

what about LWIM?

candid vigil
#

LWIM just a wrapper for all Bevy's Input without touch input

#

also handle it, let me check the source code

#

axis_pair is LWIM's dual-axis value

#

and this

midnight bramble
#

ok let my vscode finish fetching and i'll see how that works

candid vigil
#

LWIM has an Events<ActionDiffEvent> resource

slate zealot
#

In the example for twin_stick_controller it is suggested to use a player controller abstraction, would a resource be a good way to represent a player controller layer to communicate with a movement/camera system?

candid vigil
#

let me take a look. I haven't gone through the examples in detail yet. (I'm just a new contributor to LWIM, and initially, I was only fixing some bugs I came across πŸ˜‚ )

slate zealot
#

Aha πŸ˜‚

candid vigil
#

alice has quite a few oversights (whisper

slate zealot
#

Also trying to figure out where I can find documentaion on methods for LWIM

#

for example, how to read gamepad axis magnitude

midnight bramble
#

let rawInputDir = LWIM.axis_pair(&input::PlayerAction::Move).unwrap().xy();

slate zealot
#

rad, thanks πŸ™‚

midnight bramble
#

let inputDir = rawInputDir.clamp_length_max(1.0);

candid vigil
#

It should be

#

and must be

karmic monolith
slate zealot
#

I'm sure it is! I just started digging in the repo today

#

that or i just struggle navigating it

karmic monolith
#

Or use the modules / structs / traits link on the left

candid vigil
slate zealot
#

oh i see, i also had to click the documentatio button to get there

#

which took me here

#

that had the methods

#

to the left in the bottom

candid vigil
#

ahh that's true

#

BTW, my IDE's English text checker keeps showing errors in the code comments, but I'm not great at fixing them because I'm not skilled in English documentation

#

it's annoying when I have to keep dealing with text grammar errors while trying to check for code errors 😦

#

but most of the style errors are just because it thinks there shouldn't be informal writing in the doc comments, and the typos are mostly non-english words

karmic monolith
candid vigil
#

JetBrains RustRover with plugins like Grazie Pro

#

and most of general's ananotators are just Long sentences (n words here) are harder to read, according to the research; consider splitting

karmic monolith
candid vigil
#

grammar errors, for example

karmic monolith
#

It's okay if not, or if you just blindly follow the suggestions there and then we trim the back in review

candid vigil
#

if I were to just apply its suggestions, I'd only need to press a few buttons, much like cargo fix. However, many are matters of taste, and there are also some cases of simplifying long sentences. I'm currently doing a quick review to see if it breaks anything

candid vigil
#

PR done

#

reading docs is way more exhausting than coding

#

nap first, be back later

candid vigil
karmic monolith
#

Well, it's getting axed soon anyways

candid vigil
#

that's true indeed πŸ˜‚

midnight bramble
#

well, that's probably enough to make the input stream log working, just need to compare to previous to filter out repeats

#

and actually make the UI/icons working...

midnight bramble
#

i guess this works
need to turn move into distinct directions...

midnight bramble
#

@karmic monolith
not going to switch to Direction2d?

karmic monolith
#

I didn't bother to initially

midnight bramble
midnight bramble
#

yeah there's actually a lot of fun stuff

karmic monolith
#

@restive knoll FYI

slate zealot
#

So when pressing two directions at once in the twin_stick_controller example for the .axis_pair(&PlayerAction::Look).unwrap() portion I get "Looking at: [NaN, NaN]", is that supposed to happen?

midnight bramble
#

actually pressing, or neutral?

slate zealot
#

if i press both arrow keys

midnight bramble
#

let me check that...

slate zealot
#

it says [NaN, NaN], which gives me trouble when using .axis_pair().unwrap in my application

midnight bramble
#

line 200?

slate zealot
#

yes

midnight bramble
#

try changing normalize to normalize_or_zero

slate zealot
#

rad, obvious now that you say it

midnight bramble
#

or clamp, which would allow subtle aiming/movement for controllers

obtuse zinc
midnight bramble
#

i think an enum for the directions might be useful for tile-based games and fighters, not sure

obtuse zinc
#

And definitely the math ops like dir + dir = vec, f32 * dir = vec (we only have dir * f32 and only for 3D iirc), and so on

midnight bramble
#

and a function to convert direction2d to 4 or 8 from cardinal direction enum and back

obtuse zinc
#

NE, SW and so on are ordinal, only N, E, S, W are cardinal

midnight bramble
#

what's the cardinal+ordinal?

obtuse zinc
#

Compass::NE lol

midnight bramble
#

that actually sounds good, yeah

#

there's no conversion to radians for direction2d yet, yeah?

obtuse zinc
#

No

#

Would Direction2d::X be 0 radians?

midnight bramble
#

probably

#

ask math-dev

#

or i guess you can just make both versions for conversion

karmic monolith
obtuse zinc
#

converting to radians like that would be a bit weird imo, you can already do angle_between

#

Like Direction2d::X.angle_between(*Direction2d::Y)

karmic monolith
#

Since then you never screw up the reference angle

midnight bramble
#

might be useful for shaders in some way too

#

probably something else we're not even considering

obtuse zinc
#

The LWIM code looks super clean btw

midnight bramble
#

good luck upstreaming it.

karmic monolith
#

Thank you both ❀️

obtuse zinc
#

Wait, you can make doc comments like this?

/**
Maps from raw inputs to an input-method agnostic representation

Multiple inputs can be mapped to the same action,
and each input can be mapped to multiple actions.

...

**/
pub struct InputMap<A: ActionLike> {

why didn't I know that... afaik block comments like this typically aren't considered good practise but this does look cleaner for this long-ish code example

midnight bramble
#

yeah, it does

karmic monolith
#

Yeah, it also makes things easier for copy-pasting tests in (since RA hates doc-tests)

midnight bramble
#

but my main problem with docs is that when you're reading it through the file you need to scroll down to know what it's talking about

#

maybe add non-doc comments at top for titles just for the long ones?

slate zealot
#

in the twin_stick_controller example, the fn player_mouse_look is doing some wild stuff, I can't even make out where it captures mouse input data

midnight bramble
#

it's supposed to create a plane from player towards camera, so that no matter what camera rotation is, it'd always have a flat plane relative to player position

#

screenspace cursor -> worldspace origin + direction -> check if it hits that plane ->
i think diff might fail depending on orientation, but it's supposed to be direction towards the player-relative worldspace cursor
and the final step is emulating controller's stick based on that ---------^

#

yeah, if it works then it's cool but definitely not beginner-friendly

slate zealot
#

ah

#

yeah bc it has dead zones now and feel inconsistent

#

hmmm, i might not be expressing myself correctly here, but there are places where it stops rotating the camera, and I thought the function just captured mouse x/y input for the action_state

#

because I got the gamepad equivalent to function with my camera system

#

But it might be a case of me behind the keyboard

#

so I'm using PlayerAction::Look in my own camera system to rotate the camera. Works fine with the gamepad, but player_mouse_look fn seems broken, but IDK where to begin bc the fn is doing magic to me πŸ˜‚

#

also works fine with keyboard arrows

#

but as soon as I touch the mouse, there are places where rotation stops and feels inconsistent

#

3rd person, if that's what you are asking

midnight bramble
#

yeah, then you need to switch to delta, you can scrap like half of that code

slate zealot
#

Ok, I'll try some of my own chops instead, thanks for helping me understand the fn a bit better ❀️

#

how do you paste it pretty like that?

#

My god, its beautiful in here.

#

thanks

buoyant fable
#

You can put β€˜rust’ after the ticks with no space to get syntax highlighting

slate zealot
#
if let Ok(rust) = iron_in_water.get()
#

Thanks!

midnight bramble
#

anyways, FPS doesn't need you to distinguish between mouse and controller since mouse delta is similar enough to controller stick (because your cursor is locked to center, so you don't care about its' position on screen)

slate zealot
#

ah makes sense! I'm trying to get a hide cursor move camera mode and a cursor out when in UI mode kind of thing going

#

as well as the disable/enable of keyboard mouse/gamepad depending on what you use, which seemed straight forward following the example for twinstick controller

candid vigil
candid vigil
#
    pub fn input_axis_pair(&self, input: &UserInput) -> Option<DualAxisData> {
        match input {
            UserInput::Chord(inputs) => {
                if self.all_buttons_pressed(inputs) {
                    for input_kind in inputs.iter() {
                        // Return result of the first dual axis in the chord.
                        if let InputKind::DualAxis(dual_axis) = input_kind {
                            let data = self.extract_dual_axis_data(dual_axis).unwrap_or_default();
                            return Some(data);
                        }
                    }
                }
                None
            }
            UserInput::Single(InputKind::DualAxis(dual_axis)) => {
                Some(self.extract_dual_axis_data(dual_axis).unwrap_or_default())
            }
            UserInput::VirtualDPad(dpad) => {
                let x = self.extract_single_axis_data(&dpad.right, &dpad.left);
                let y = self.extract_single_axis_data(&dpad.up, &dpad.down);
                Some(DualAxisData::new(x, y))
            }
            _ => None,
        }
    }
#

source code for axis_pair

#

should it just be None when the values are [0.0, 0.0]?

#

this function hasn't been checking that since version 0.5 when alice added it

#

ah, so, None just means the data isn't available there

#

this should be fine

slate zealot
#

How does one map both arrow_keys and mouse_motion to one player action? Is it even possible?

candid vigil
#

how about the const arrow_keys() and mouse_motion() creations of VirtualDpad?

#
let input_map = InputMap::default()
  .insert(Action, VirtualDpad::wasd())
  .insert(Action, DualAxis::mouse_motion());

// or
let input_map = InputMap::new([
  (Action, VirtualDpad::wasd()),
  (Action, VirtualDpad::mouse_motion()),
]);
#

but VirtualDpad doesn't have value sensitivity

#

and deadzone

slate zealot
#

from the twin_stick_controller example: ```rs
impl PlayerAction {
fn default_kbm_binding(&self) -> UserInput {
// Match against the provided action to get the correct default gamepad input
match self {
Self::Move => UserInput::VirtualDPad(VirtualDPad::wasd()),
Self::Look => UserInput::VirtualDPad(VirtualDPad::arrow_keys()),
Self::Look => UserInput::Single(InputKind::DualAxis(DualAxis::mouse_motion())),
Self::Shoot => UserInput::Single(InputKind::Mouse(MouseButton::Left)),
}
}
}

candid vigil
#

ahh

#

fix them in PR later

#

but you could split it into two functions: default_keyboard_binding and default_mouse_binding

#

and I feel like this way would be better:

#
    fn default_input_map() -> InputMap<PlayerAction> {
        let mut input_map = InputMap::default();

        // Default gamepad input bindings
        input_map.insert(Self::Move, DualAxis::left_stick());
        input_map.insert(Self::Look, DualAxis::right_stick());
        input_map.insert(Self::Shoot, GamepadButtonType::RightTrigger);

        // Default kbm input bindings
        input_map.insert(Self::Move, VirtualDPad::wasd());
        input_map.insert(Self::Look, VirtualDPad::arrow_keys());
        input_map.insert(Self::Shoot, MouseButton::Left);

        input_map
    }
#

make it easy to add more stuff later on.

slate zealot
#

No need to fix!

candid vigil
#

in PR, i added InputManagerBundle::with_map(InputMap) allowing creating the bundle with the given InputMap and default ActionState

#
// before
InputManagerBundle {
  input_map,
  ..Default::default()
};
// after
InputManagerBundle::with_map(input_map);
candid vigil
#

@karmic monolithi thought it's pretty clearer. could we switch to this?

midnight bramble
candid vigil
#

vec2.length().clamp(0.0, 1.0)?

#

it's a direction in the example

#

so it should be normalized

#

direction2d

#

until we have Bevy's rotation2d

candid vigil
#

@karmic monolith would you like to create a new branch to avoid causing chaoes directly to the main branch until the new system has been fully tested?

#

many parts of the UserInput trait seem to be ready

midnight bramble
#

wait, let me see the example...

#

yeah there's no real conflict there

#

clamping movement feels better when you can use full circle instead of snapping to max speed
normalizing look usually feels bad

candid vigil
#

could you provide any idea for

midnight bramble
# candid vigil could you provide any idea for

i've been digging through LWIM code yesterday to get a better idea of how it works in the first place
for now i think there's a much better immediate goal of changing direction to direction2d, upstreaming all useful functions and fixing LWIM to work with direction2d
i also want to work on my game a little more to make abusive LWIM stress test to make sure what i'd definitely want from it, and what rework might break

#

for now my goal is to make a crappy imitation of unity's input system binding menu

#

and the idea is that at runtime players should rebind whatever action they want to whatever they want, so e.g. if somebody wants to assign any button to be a chord of a stick at a certain angle plus a button, it should be possible

#

today i sidetracked because actions and etc would need to have translations, and i have no idea if bevy already has something built-in or planning to upstream anything

#

@candid vigil the twin stick example's stick emulation for mouse would be really good as built-in option in LWIM

#

i'm cleaning up my camera+reticle code right now, to see if it's a good idea or if it's better to just keep it as example

candid vigil
midnight bramble
#

nope

#

i thought jondolf upstreaming half of the stuff there to dir2d would make it cleaner but i guess doing it like that is fine too

candid vigil
#

yeah LWIM has 8 direction constants, while Bevy only has 4

midnight bramble
#

jondolf might add them, or the new compass enum. it just sounds really useful.

candid vigil
#

@karmic monolith could you take a look at #489, and then I'll go ahead and push a PR about the current progress of #483?

karmic monolith
candid vigil
#

thank you!

amber wind
#

@karmic monolith Couldn't find a thread for leafwing_abilities, so I'll just post it here.

whilst porting my personal project to bevy 0.13, I realised I need better input and abilty management, and decided that leafwing_abilities was good. but since it wasn't updated, I did it myself:

https://github.com/Leafwing-Studios/leafwing_abilities/pull/45

GitHub

Wanted to use this crate for my new project, so decided to help update it. But with the recent removal of methods like ActionLike::variants(), I am not sure how to properly change the charges syste...

midnight bramble
midnight bramble
#

the only thing i'd need is knowing where kbm and controllers switch to change UI icons, and to prevent same action from being used from 2 devices, but i doubt input map helps with that...

#

i guess it makes sense when you don't want it to track touch/xr/controller when you don't even have one

karmic monolith
candid vigil
#

InputMap itself is easy to expand, much like a HashMap.

candid vigil
#

I'm in the process of replacing the current SingleAxis, DualAxis, and dead zone settings with pre-processing, similar to Unity, for both single-axis and dual-axis

midnight bramble
#

keep rebinding in mind

#

but yeah, in unity the action itself decided if it was button, axis, dual or 3d

#

and you could assign/emulate anything with that

candid vigil
#

but shouldn't handling specific directions be another input preprocessing, rather than the responsibility of the deadzone?

midnight bramble
#

what do you even mean by preprocessing

candid vigil
#

ohhh, it should be called processing

midnight bramble
#

yeah that makes sense then

#

deadzone is configurable for input, not action

candid vigil
#

I misread that

midnight bramble
#

i know that bevy controller has deadzone but i'm not sure how it was supposed to be edited, if LWIM allowed that + remapped from deadzones to 0-1 it'd be good

candid vigil
#

well, actually, there is, but it's implemented in a bit of a weird way. I'm working on writing a new one.

#

src/axislike.rs in LWIM

midnight bramble
#

but the core function/workflow of input manager was super simple
you make a "map" that you can easily switch between (e.g. walking, driving, flying)
then you make an action in that map, and decide if it's button/axis/dual/3d
the rest can be set up/rebound/added/removed by user

candid vigil
#

and the current one only has min, not max

#

but now i wanna replace it with input processors, like unity

midnight bramble
#

mm...

normalize/clamp should be inside action, inaccessible to users

deadzone, scaling, and invert should be accessible to users

#

no idea what custom processors are used for

candid vigil
midnight bramble
#

but i think the main thing is allowing users to bind anything to any action without having devs think about it

candid vigil
#

sure, but now i'm just working on rewriting the underlying architecture of LWIM, allowing more extensions

#

the current architecture is terrible

#

a lot of hardcoding

midnight bramble
#

πŸ€” i don't like the idea of solving problems ahead of time, but if you have a clear idea of what you want then why the hell not

candid vigil
#

yeah, that's why I'm trying to...

midnight bramble
#

yesterday i reworked the camera + reticle for my game, changing from a camera-relative movement + aiming to plane-relative

controller stick (or dpad/arrows) takes input, clamps it (<-- this could be done for the action, can't do it right now because player-added input wouldn't be normalized),
then multiplies by max range and places reticle relative to plane and focus point

mouse is similar to twin stick example, takes cursor, converts to worldspace, intersects with plane...
and then clamps length by max range to make same thing as controller stick.

i wanted both mouse and controller to work at the same time since i've seen that some people play with controller in 1 hand and mouse or keyboard in the other

so to allow both of them to work i ended up comparing sq length and just picking the highest
this basically meant that mouse couldn't be used for the same input as controller, otherwise mouse delta conflicts with mouse position
i'm not sure if it was the best way to do it, but there's probably going to be another problem with VR/touch/ui later so i don't want to think about it right now

i still have to try it out with FPS and anti-gravity, but yeah...
LWIM doesn't really need to do much, and so far i didn't find anything new that i want that i didn't mention πŸ€·β€β™‚οΈ

candid vigil
#

I'm in the process of replacing the current SingleAxis, DualAxis, and dead zone settings with input processing, similar to Unity, for both single-axis and dual-axis

#

but shouldn't handling specific directions be another input processing, rather than the responsibility of the deadzone?

midnight bramble
#

if you have a steering wheel and want steering right to be used as RMB and steering left to be used as LMB then it makes sense to add as processor, yeah

candid vigil
#

"takes cursor, converts to worldspace, intersects with plane", the new version should have a bulit-in function

midnight bramble
#

yeah, i did it partially to understand if it should be

candid vigil
#

just because the current architecture is hard to add new extensions

#

that's because the current internal system has abstracted all supported input methods into three types: button-like, single-axis-like, and dual-axis-like.

midnight bramble
#

plane-relative movement+reticle sounds like it could be built-in but i want to finish messing around with FPS, anti-gravity and using focused point to try lagging behind/looking ahead of player, and doing the cinematic sweeps

candid vigil
#

so it's about handling inputs based on these three abstractions, rather than dealing with each input type separately

midnight bramble
candid vigil
#

but that's why it's hard to add new extensions

midnight bramble
#

float, vec2, vec3 need to be known ahead of time for making game

candid vigil
#

the real world not only these input types: button-like, single-axis-like, and dual-axis-like

midnight bramble
#

yeah but you convert any input to these 3-4 types

#

if you want vec2 then you make a virtualdpad wasd

#

or a dualaxis controller stick

#

no idea if it supports using stick as a button by direction right now though

candid vigil
#

for example, XR inputs and gestures, triple-axis-like, multiple f32, multiple Vec2, multiple Vec3

#

and the current system is handling the input values in a single huge function

midnight bramble
#

yeah gestures and triple-axis are necessary, don't know enough about the rest

#

and adding gestures to XR also implies you can add them for mouse

candid vigil
#

multiple finger gesture requires multiple f32 and multiple Vec2

#

for multi-fingered gloves XR, multiple Vec3s are also needed, as they deal with 3D input data

midnight bramble
#

yeah, i have no idea how it's done so it's better to check how unity did that

#

they went pretty hard on XR support

candid vigil
#

yeah, i'm checking the unity documentation

midnight bramble
#

maybe also ask around #xr

candid vigil
#

and in the future, XR might even support turning every joint of a person into an input point, so players can control characters just like controlling our own bodies, right?

#

even though it might be a while before we see that

midnight bramble
#

there's probably a certain point where it's no longer a concern for LWIM though

candid vigil
#

yeah

midnight bramble
#

it only needs to unify controls where it can, and give access to do it manually for the rest

#

i can see the thing i did being standardized though

candid vigil
#

i think all the input handlings should be able to upstream into Bevy

#

and LWIM just work as an input-action binding manager

midnight bramble
#

cursor ray -> pointer ray
camera movement/looking at a point between focus and pointer -> head tracking
just separating gameplay, camera, "pointer", and reticle in general

candid vigil
#

yeah, my planned new user inputs, CursorWorldPosition and CursorScreenPosition

#

they're hard to add into the current LWIM

#

because LWIM obtains input values from the Resources currently

#

it cannot support get values from a Query right now

#

that's why i wanna split the existing terrible InputStreams

midnight bramble
#

the problem with cursor is that you might not have it

#

like on console or mobile

candid vigil
#

yeah

#

so all the new user inputs will return their values wrapped in an Option

#

because sometimes the input devices are also unavailable

#

and ClickedWorldPosition and ClickedScreenPosition for both mice and mobiles

#

and ClickedEntity? like bevy_mod_picking

midnight bramble
#

and if it's not available then it shouldn't be polled anyway

#

i'd rather have the default values

#

there's only 1 case where i think option would be useful and that's pausing the game when device is lost, but that should be handled by LWIM too

candid vigil
#

but shouldn't you consider adding support for multiple input devices in your game? Like keyboard and mouse, gamepad, and mobile?

#

if one kind of action only binds with a specific input device, like cursor edge panning

#

and the device is unavailable, it should return None instead of [0.0, 0.0]

#

or Result<ValueType, InputError>?

candid vigil
midnight bramble
#

ah, yeah

candid vigil
#

only one line would be added, or you wanna print the panic information

candid vigil
#

and @karmic monolith

midnight bramble
#

if this is user-configurable then clamp should be moved out of it, otherwise someone adds new input or removes a processor to gain ~30% more diagonal move speed

candid vigil
#

ahh, yeah

midnight bramble
#

i don't understand for reject all values, is that like disabling device

candid vigil
#

just a thought for a possible configuration?

midnight bramble
#

and .invert_ is already there in leafwing so it's not entirely new, clamp/normalize too, not sure what else

candid vigil
#

but the existing ones requires branch logic during the computation

#
    /// The sensitivity and inversion of the input.
    ///
    /// Using a single `f32` here for both sensitivity and inversion
    /// improves performance by eliminating the need for separate fields and branching logic.
    ///
    /// The absolution value determines the sensitivity of the input:
    /// - `1.0` indicates no adjustment to the value
    /// - `0.0` disregards input changes
    /// - Values greater than `1.0` amplify input changes
    /// - Values between `0.0` and `1.0` reduce input changes
    ///
    /// The sign indicates the inversion:
    /// - Positive values indicate no inversion.
    /// - Negative values indicate inversion.
    pub multiplier: f32,

there's my optimized ones

#

inner implementation

midnight bramble
#

ohh, good idea

candid vigil
#

just multiply it

midnight bramble
#

action will need to be clamped or normalized before these then

#

or not... πŸ€”

candid vigil
#

yeah, there's just one type of processor

#

you could add any you wanted

midnight bramble
#

for FPS camera it makes sense, since it doesn't need to clamp/normalize...
for movement input it will need to be clamped afterwards...
if there's a case that movement needs to be circular but allowing sensitivity to be customizable, it'd need to be clamped beforehand

#

all of it is not for user to decide though

candid vigil
#

yeah, so the implemented one just a AxisInputProcessor, and the addition of all types of the processors just its functions

midnight bramble
#

so basically the action will need an option to disable clamp(or normalize) / clamp(or normalize) before / after user-defined processor

candid vigil
#

if you add a new same one, the old processor will be replaced

midnight bramble
#

maybe instead of processor, call it user settings?

candid vigil
#

that's what I used to call it, but I'm not sure which is the best name

midnight bramble
#

user settings are more self-explanatory

#

processor would be better for dev side of it

candid vigil
#

yeah

open trout
midnight bramble
#

if it can be done by input manager then why not though

open trout
#

clamping doesnt make sense in all scenarios

#

sometimes diagonal input is supposed to be faster

midnight bramble
#

yeah, that's why it should be an option

open trout
#

alright im down for that then

midnight bramble
#

and if input manager takes care of user settings and you need clamping before it's inverted or multiplied then it'd become difficult unless clamping option is built-in

candid vigil
midnight bramble
foggy osprey
#

I have UI buttons hooked up to actions, and when I just_pressed those actions, it reports them pressed for every frame the UI button is held down. Is this a known issue?

karmic monolith
foggy osprey
#

I'm looking through the examples, and it looks like the only ones that use just_pressed don't have UI hookups, so it could've slipped through. Do you need a minimal repro?

foggy osprey
#

But I'd much rather present a solution with the problem, so I'm happy to take the first stab at this

karmic monolith
tiny fern
#

i also noticed just_pressed doesn't work with me on 0.12.1 today

#

just doesn't work, even if i schedule the systems that reads inputs after the tick system

#

to be honest i really balk at using an individual system for every single action state, plus using the ECS for literally every problem in general-- thrashing the scheduler for no reason, to read a few inputs?

#

otherwise the API looks really nice but i am confused why bevy doesn't have anything other than an ECS, it is weird to me

gentle compass
#

hey I uhhhh was using the variants() method on Actionlike. but it's gone in the latest version and idk how to get the equivalent behavior

karmic monolith
tiny fern
#

am i supposed to read the button state before the tick function

#

i mean that works

midnight bramble
gentle compass
#

but I think (from looking at recent examples) you guys have implemented the behavior I wanted so I might just need to update all my logic instead of the weird stuff I was doing before haha

candid vigil
#

I'm still debugging all the new deadzone types

#

The current behavior of the Ellipse deadzone is actually incorrect because ideally, a circular deadzone should smoothly normalize all input outside the deadzone, but it's not doing that properly

#

So, I'm rewriting the implementation for all deadzones

#

The new circular deadzone looks fine after I've tested all xy values within the range [-1.0, 1.0] with a step of 0.01

#

However, there are still some issues with the new rounded square deadzone, especially around the corners

candid vigil
#

now more work is on Alice's plate πŸ˜‚

#

1416 lines of code and 629 lines of comments

#

half of the code consists of unit tests

#

PR created, but the new implementation haven't been used to replace the old ones yet

karmic monolith
candid vigil
#

I should use Box...

midnight bramble
#

wait, bytes?

#

not bits?

#

what exactly is the largest variant?

candid vigil
#

"the trait bevy::prelude::FromReflect is not implemented for std::boxed::Box<input_settings::axislike_settings::SingleAxisSettings>"

#

@karmic monolith how do i implement Reflect manually?

karmic monolith
#

Very easy to screw up

#

Don't worry about boxing it for now: this will get cleaned up in the trait refactor anyways

candid vigil
# candid vigil

clippy is really unhappy and threw a bunch of errors at me

#

this will prevent the CI tests from passing completely

karmic monolith
candid vigil
#

ohhhh, i forgot it

#

should be ready for review

#

passed all CI tests on my computer

candid vigil
#

this PR isn't urgent. I might not be able to continue with the next stage of trait refactor for another two weeks. The holidays are over, and I'm busy with work for these two weeks as well

tiny fern
#

it is kind of weird imo that the allocation type of an object is tied to so much syntax in rust

#

in c# you add sealed, now the compiler can optimize away dynamic dispatch

#

in nim you add ref, now it’s on the heap instead of the stack

#

in rust, you add Box<T> and go refactor 200 lines of code, break all your clients

#

or swap from enum to trait which is also weeks of work

#

I am not rly certain rust got it right, I am actually confused how it got such a huge following with the sheer level of complexity and toil for absolutely minimal gains, I believe u can design a memory safe language that doesn’t have so much toil and explicitness without all this headache when u have to do.. anything

#

I just use it because I don’t wanna write c++ and nim doesn’t have a stable game dev ecosystem

#

but how are u guys not driven crazy by the limitations and toil you have to go through to accomplish simple things.. there’s a reason these other languages got these features, to reduce toil

#

I had to create 4 types, all with confusing names/responsibilities, just so I could do some really basic stuff with dynamic dispatch

#

trait for clients to implement (which has associated cost, so not object safe), trait to be dynamic over it (DynX), newtype to specialize over it (BoxedX)

#

it doesn’t have to be like this.. no other lang is this insaneβ€” I’ve seen other ppl use these patterns all over big rust libs but nobody saying how awful they are

#

Also, sorry to derail your thread, I just think it’s an interesting talking point that was relevant and literally nobody will engage this kind of talk in the rust community in an unbiased way

#

u should just delete all that but it’s worth a read to see how the rest of the world works if you are annoyed at your epic Box/Trait refactor

midnight bramble
#

@tiny fern seems like it's great for discouraging people from using heap though

tiny fern
#

premature optimization at the cost of everything

#

well not everything, but half the dev worlds sanity

midnight bramble
#

true, but at the same time you know that people will always try to use the easy way to get shit done and it's always a disaster

tiny fern
#

I feel like rust could be even more popular if it wasn’t so allergic to trad patterns for nooooo reason

#

the majority of ppl I know in the industry literally thinks rust devs are insane

#

they also include me, and I understand why

#

they like their features and I also hate not having em

midnight bramble
#

so for now i'm just taking it as rust nudging me to write better code

#

but yeah, your point is definitely valid

tiny fern
#

there are features in the pipeline to reduce toil too even in the specific β€œ4 types” example I gave so it’s rly not ultra fair but

#

still painful right now

buoyant fable
# tiny fern <a:CATMONCH:1069080835975815178>

I can't remember the last time I had to add Box to a type. Like, I generally know when I am going to need dynamic dispatch when I am writing the code the first time. You got me with the Enum/Struct refactoring through, that's always a pain. Really wish they just only had algebraic data-types types with one or more constructors.

gentle compass
#

is it still "okay" to add a LeafwingActionState<T> directly to an entity and later access and directly modify it within a query? I'm trying to get the underlying ActionData by using .action_data_mut but I keep getting a None value back and I'm not sure why? Does it not come initialized and I need to handle the ActionData not existing initially?

karmic monolith
gentle compass
candid vigil
#

this function will add a default value to the internal hashmap when the action data is missing, and return a &mut of the newly added one

candid vigil
#

could you explain the problem with this is what exactly? If this can be implemented, I can then add a user-defined input processor

karmic monolith
#

I'm pretty sure that this has been proven to not be the case, but validation of that and a concrete use case would be helpful to drive it forward

#

(I really want this too)

candid vigil
#

that's a pity

#

but are there any tutorials on how to implement FromReflect? I think it should be fine if it's just to implement a &dyn InputProcessor

#

which just a trait with a fn process(f32) -> f32

karmic monolith
gentle compass
foggy osprey
#

Alright, I'm totally lost. This seems to be what's pressing the buttons:

#

And this seems to be what's releasing it (specifically the ButtonState::Released match arm):

#

(This is for UI buttons triggering just_pressed repeatedly btw)

#

Near as I can figure, this is because which_pressed goes through the list of Actions that need to have their inputs updated and finds that "whoopsie, nobody's pressing any of the key bindings for this Action, let's release it"

#

Then update runs on that information and releases the button. Unfortunately that means that it's released next time the UI interaction system runs, and the button gets newly pressed again

#

At a high level, do you think it's a good idea to maybe add a UI interaction field to InputStreams? Then which_pressed could treat it as just another way to trigger an action

foggy osprey
#

Hmmm, actually that would require adding a new InputKind. And that would require reworking how UI buttons even interact with actions. Right now you can wire up a button to an action, but what I'm proposing would end up with wiring actions to buttons, just like how keyboard works now:

InputMap::new([
    (Action::Left, UiInteraction(left_button)),
    ...
])

instead of:

left_button.insert(ActionStateDriver {
    action: Action::Left,
    targets: player.into(),
})
...
#

Before I even go down this road, is this counter-productive/even what we want? We could keep the current API with some hack or other in update_action_state instead. What's the preference here?

karmic monolith
foggy osprey
#

Awesome, I'll check out those PRs and see how they affect my current problem

#

@karmic monolith I'm going to open an issue for this specific problem so I have something to link and discuss with @candid vigil, but it doesn't need any extra attention πŸ‘

foggy osprey
#

Do action states read properly in fixed update schedule? Or just in update?

karmic monolith
candid vigil
#

but it looks like we won't need ActionStateDriver anymore

karmic monolith
untold thorn
untold thorn
#

@karmic monolith I have looked into it. My specific issue is not about the repeated just_pressed inputs but it is indeed connected. Considering Interactions as input types may allow to fix it (like mentioned here: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/483#issuecomment-1972125204 by the same issue author).

What is happening in my case is that:

I can work around this by setting the "no_ui_priority" feature for Leafwing Input Manager in my game until #12007 in Bevy is solved.

karmic monolith
frank charm
#

Question -

How would you architect different 'sensitivity' configurations for different inputs driving the same action?

Usecase - I want to support panning the camera in a 2d game with a virtual DPad or with a 'click + drag' mouse action.

This is really easy to configure, the mouse motion is buttery smooth and great, and the virtual Dpad is slow as molasses in Jan.

#

(Also just as a random aside, this isn't exactly a surprising problem but I -really- would've assumed it'd go the other way and I'm sort of confused as to how it doesn't)

restive knoll
#

iirc there was an issue with the speed of non-mouse inputs being FPS bound tho ... Idk if that's been fixed yet

grand zinc
#

guys there are the way to get combined action state for every registered actionlike? or i need manually create "global" actionlike component with all actions i need and then query it?

gentle compass
#

then you can just register a system per action you have

loud shale
#

I want to move the camera by dragging with the mouse so I found this example here
https://github.com/Leafwing-Studios/leafwing-input-manager/blob/main/examples/mouse_motion.rs
which is basically what I need I just want to hold the left mb as well so I read the docs and found chords and changed the code to this

let mut input_map = InputMap::default();
    input_map.insert_chord(CameraMovement::Pan, [DualAxis::mouse_motion(), MouseButton::Left]);

which doesnt work because those inputs are different types

#

is there a canonical way to make this work?

karmic monolith
#

Instead I would check the logic within one of your own systems, and use an action to abstract over the non-drag part of the input

loud shale
#

alright thank you

candid vigil
#

Why doesn't input_map.insert_chord(CameraMovement::Pan, [DualAxis::mouse_motion(), MouseButton::Left]) work?

#

IIRC [VirtualDPad::mouse_motion(), MouseButton::Left] working

loud shale
#
error[E0308]: mismatched types
  --> src/main.rs:20:76
   |
20 |     input_map.insert_chord(CameraMovement::Pan, [DualAxis::mouse_motion(), MouseButton::Left]);
   |                                                                            ^^^^^^^^^^^^^^^^^ expected `DualAxis`, found `MouseButton`
candid vigil
#

ohhh, add .into()

loud shale
#

to what?

candid vigil
#

InputKind or UserInput

#

[DualAxis::mouse_motion().into(), MouseButton::Left.into()]

#

this should be fine

loud shale
#
    |
209 |     pub fn insert_chord(
    |            ------------ required by a bound in this associated function
...
209 |     pub fn insert_chord(
    |            ------------ required by a bound in this associated function
...
212 |         buttons: impl IntoIterator<Item = impl Into<InputKind>>,
    |                                                ^^^^^^^^^^^^^^^ required by this bound in `InputMap::<A>::insert_chord`
help: try using a fully qualified path to specify the expected types
    |
20  |     input_map.insert_chord(CameraMovement::Pan, [<leafwing_input_manager::axislike::DualAxis as Into<T>>::into(DualAxis::mouse_motion()), MouseButton::Left.into()]
    |                                                  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++                        ~

tried the suggested fix for UserInput doesnt work cause From isnt implemented for that and for InputKind it crashes at runtime

#
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(InputManagerPlugin::<CameraMovement>::default())
        .add_systems(Startup, setup)
        .add_systems(Update, pan_camera)
        .run()
}

#[derive(Actionlike, Clone, Debug, Copy, PartialEq, Eq, Hash, Reflect)]
enum CameraMovement {
    Pan,
}

fn setup(mut commands: Commands) {
    let mut input_map = InputMap::default();
    input_map.insert_chord(CameraMovement::Pan,
                           [<leafwing_input_manager::axislike::DualAxis as Into<InputKind>>::into(DualAxis::mouse_motion()), MouseButton::Left.into()]
    );
    commands
        .spawn(Camera2dBundle::default())
        .insert(InputManagerBundle::with_map(input_map));

    commands.spawn(SpriteBundle {
        transform: Transform::from_scale(Vec3::new(100., 100., 1.)),
        ..default()
    });
}

fn pan_camera(mut query: Query<(&mut Transform, &ActionState<CameraMovement>), With<Camera2d>>) {
    const CAMERA_PAN_RATE: f32 = 0.5;

    let (mut camera_transform, action_state) = query.single_mut();

    let camera_pan_vector = action_state.axis_pair(&CameraMovement::Pan).unwrap();

    // Because we're moving the camera, not the object, we want to pan in the opposite direction.
    // However, UI coordinates are inverted on the y-axis, so we need to flip y a second time.
    camera_transform.translation.x -= CAMERA_PAN_RATE * camera_pan_vector.x();
    camera_transform.translation.y += CAMERA_PAN_RATE * camera_pan_vector.y();
}

Heres the full code now

candid vigil
#

I see why it doesn't work. It's a bit inconvenient, but I understand it's because of the two layer nesting of InputKind and UserInput

#

I mostly use InputMap::new() for different types of inputs, and I rarely use InputMap::insert_chord()

loud shale
#
fn pan_camera(mut query: Query<(&mut Transform, &ActionState<CameraMovement>), With<Camera2d>>) {
    const CAMERA_PAN_RATE: f32 = 0.5;

    let (mut camera_transform, action_state) = query.single_mut();

    if let Some(camera_pan_vector) = action_state.axis_pair(&CameraMovement::Pan)
    {
        // Because we're moving the camera, not the object, we want to pan in the opposite direction.
        // However, UI coordinates are inverted on the y-axis, so we need to flip y a second time.
        camera_transform.translation.x -= CAMERA_PAN_RATE * camera_pan_vector.x();
        camera_transform.translation.y += CAMERA_PAN_RATE * camera_pan_vector.y();
    }
    
}

that fixed it

#

dont get how it could be None tho

candid vigil
#

A chord is only active when all of its inside buttons are pressed

#

so if the left mouse button isn't pressed or there's no mouse movement, it returns none

#

returning none means either this action has no dual-axis input bound to it, or the input is not active like above

loud shale
#

ahh and I guess in the original example mouse_motion is always active so it can never be None

candid vigil
#

yeah

loud shale
#

makes sense thank you

candid vigil
#
    pub fn input_axis_pair(&self, input: &UserInput) -> Option<DualAxisData> {
        match input {
            UserInput::Chord(inputs) => {
                if self.all_buttons_pressed(inputs) {
                    for input_kind in inputs.iter() {
                        // Return result of the first dual axis in the chord.
                        if let InputKind::DualAxis(dual_axis) = input_kind {
                            let data = self.extract_dual_axis_data(dual_axis);
                            return Some(data.unwrap_or_default());
                        }
                    }
                }
                None
            }
            UserInput::Single(InputKind::DualAxis(dual_axis)) => {
                Some(self.extract_dual_axis_data(dual_axis).unwrap_or_default())
            }
            UserInput::VirtualDPad(dpad) => {
                let x = self.extract_single_axis_data(&dpad.right, &dpad.left);
                let y = self.extract_single_axis_data(&dpad.up, &dpad.down);
                Some(DualAxisData::new(x, y))
            }
            _ => None,
        }
    }
#

the source code

random sparrow
#

how do i toggle an action/state with the same key? e.g. for a pause menu i have this, but obviously i want to be able to unpause the game as well:

fn pause_game(
    mut time: ResMut<Time<Virtual>>,
    pause_action: Query<&ActionState<GameAction>>,
    mut menu_state: ResMut<NextState<MenuState>>,
    mut game_state: ResMut<NextState<GameState>>,
) {
    let pause_action = pause_action.single();
    if pause_action.just_pressed(&GameAction::Pause) {
        time.pause();
        menu_state.set(MenuState::Paused);
        game_state.set(GameState::Paused);
    }
}
midnight bramble
random sparrow
#

because i'm clearly doing it wrong. how would you do it

midnight bramble
#

if state is paused then unpause, else pause?

candid vigil
#
error[E0204]: the trait `std::marker::Copy` cannot be implemented for this type
  --> src\input_settings\single_axis_settings.rs:45:24
   |
45 | #[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
   |                        ^^^^
...
88 |     processors: Vec<Box<dyn SingleAxisInputProcessor>>,
   |     --------------------------------------------- this field does not implement `std::marker::Copy`
   |
#

should I temporarily remove the Copy for UserInput and InputKind in the PR #494?

#

I'm also not sure how to name the input processors. For example, should I call it SingleAxisInputProcessor or SingleAxisProcessor? InputClamp or InputLimit? Deadzone2d or DualAxisDeadzone?

karmic monolith
karmic monolith
karmic monolith
candid vigil
#

should not InputKind::SingleAxis and DualAxis store a corresponding settings?

#

After the trait refactoring is completed, I expect it to look like this: (CameraAction::Rotate, MouseMotion::new().sensitivity(2.0).deadzone_symmetric(0.1).normalize_into(-1.0, 1.0))

karmic monolith
candid vigil
#

I personally think that InputKind will be replaced by UserInput implementors, and only InputKind has the Copy trait, but not UserInput

#

and internally, InputMap actually uses UserInput which only has the Clone trait, not InputKind which has the Copy trait

#

In addition, InputStreams will become a place to simply store input data instead of being responsible for processing data. The processing part will be done by the new UserInput implementors

karmic monolith
#

Okay yeah, let's proceed down this path for now

#

And unblock the rest of the refactoring

candid vigil
#

I'm not sure if my idea can be perfectly implemented, but I can't find a good way to integrate the existing Vec<Box<dyn Processor>> and InputKind for now

#

or temporarily replace InputKind::SingleAxis and peers with UserInput::SingleAxis?

#

This should minimize the changes and avoid removing the existing Copy derive for InputKind

candid vigil
#

and which name sounds better, SingleAxisProcessingChain or SingleAxisProcessingPipeline?

karmic monolith
#

Latter

#

More clear to me

candid vigil
#

uhhh I ran into some problems that I caused myself πŸ˜…

#

The main problem is that I implemented a UserInputIter to iterate over the internal InputKinds during the previous refactoring. This simplified the internal processing of Clashing Check, N-Matching, RawInputs conversion, and InputStreams

#

However, now if it is UserInput::SingleAxis, it cannot get an InputKind that holds a ProcessingPipeline. Maybe I still have to remove Copy

narrow pasture
#

Maybe this is just my brain being rather syrup-y today but the documentation of Rotation seems rather conflicting (compared to its implementation)? Both from_degrees and from_degrees_int say that they construct a rotation measured counterclockwise from the positive x-axis...but then new and micro_degrees say that they construct a rotation/return microdegrees measured clockwise from midnight/the positive y-axis, and the math of those functions does not seem to be doing anything to resolve the supposed difference

candid vigil
#

LWIM's Rotation may be replaced by Bevy's newly added Rotation2d in its 0.14 version

candid vigil
#

Most of the code and documentation for PR #494 are done (haven't push yet), but do I need to separate the new Deadzones, like the diamond and rounded square, into a new PR?

#

Also, I'm currently considering adding a visual example like the one in this video

#

in this way, we can provide an intuitive way for users to understand the differences between various Deadzones

candid vigil
midnight bramble
candid vigil
#

All the code, documentation, and document examples of processors are done, except for debugging the macro rules and missing visual examples

karmic monolith
#

Are you ready for me to review and merge #494 now?

candid vigil
#

I've been working on defining some macro rules that can help us simplify our code and improve our efficiency, but I want to make sure they're thoroughly tested before I share them with you

#

I'm expecting to finish testing by tonight in CN time or tomorrow morning in US time

#

please don't feel rushed πŸ˜‚ 3000+ lines updated but the source code just about 800 lines, most of others are documentation

karmic monolith
#

That's the sort of ratio we like around here πŸ˜‰

signal quiver
#
let move_delta = action_state
    .clamped_axis_pair(&input::PlayerAction::Move)
    .unwrap()
    .xy()
    * time.delta_seconds();

// used with bevy_rapier2d's KinematicCharacterController.
controller.translation = Some(move_delta * 200.0);

This seems to be nicely normalised for gamepads, but using the VirtualDPad::wasd() has the diagonals moving faster. I tried .normalize(), keyboard works as expected, but now the gamepad stopped being analogue and just instantly goes between 0 and max.

It's probably something silly on my side, but I've been scratching my head a while.

slate zealot
#

@signal quiver are you using a state to switch between gamepad/keyboard input? I just added to my movement function so that if it is in keyboard state, it will normalize the input to get strafe bonus out of the game

signal quiver
#

No, not yet, I can try that, though. I just didn't get there as I thought I was doing something wrong. πŸ˜„

signal quiver
candid vigil
#

The new parts (haven't been pushed) of #494 will fix this

#

I've added four types of normalizers, each handling circular deadzones or square deadzones normalized to circular input bounds or square input bounds

#

the circular input ones will remove the diagonal acceleration

#

ohhh, #494 don't fix that for VirtualDPad now

#

but VirtualAxis and VirtualDPad should also be able to add input processors

candid vigil
#

When I wake up, I'll push #494, just got a bit of work left to finish

candid vigil
#

Update the description for #494 and the related issue, but the code hasn't pushed since a strange bug for reflection

candid vigil
#

I'm unsure about the naming convention; some games and Unreal use terms like AxialDeadzone and RadialDeadzone, while others prefer SquareDeadzone and CircleDeadzone

karmic monolith
#

Latter

#

Plain language where possible

candid vigil
#

yeah, I use the terms in the documentation but call them as the latter

#

how about other names in #491?

#

should we need to add some processors like abs and those commonly used in machine learning, such as ReLU, sigmoid, and tanh?

midnight bramble
#

would ML use LWIM though

#

either way i think you can add it afterwards

karmic monolith
#

Yeah let's wait to see what users need

candid vigil
#

#494 is ready for review

#

I'm not sure if these new processors need to be registered with 'register_type'. I didn't see any issues in my tests even without registering them

#

Maybe adding AxisInspection processors is better for debugging? But I'm also not sure whether they should be used with dbg! or println! or some other approach.

karmic monolith
#

Good to do

#

Although I guess it might be less necessary now that we have recursive type registration

karmic monolith
candid vigil
#

No worries! Life comes first. As long as we can get it sorted before LWIM 0.14 rolls out, that'll be perfect.

karmic monolith
#

Yep!

#

Thank you for all your hard work on this

candid vigil
#

Do these processors need to implement Display? Because in my implementation, many fields in constructors are replaced with precomputed internal fields used to eliminate redundant calculations

karmic monolith
umbral linden
#

I wonder what you think about it

karmic monolith
topaz dome
#

How can I add mouse position as a action? I saw a example "mouse_position" but my code is giving a error at action_state.action_data_mut(&driver.action).unwrap()

candid vigil
#

Do we still need DualAxisData? The only difference currently is its built-in function to convert to Rotation. However, we should also switch to Bevy's Rotation2d in 0.14

candid vigil
topaz dome
#

Yes, I'll upload

candid vigil
#

Ohh I see, this part was updated in my codebase last month, but it hasn't been merged (#494).

#

The functions action_data and action_data_mut are quite confusing

#

I think it would be better to refactor their implementation based on action_data_mut_or_default

#

Or rename them to *_unchecked

#

πŸ˜‚ Alternatively, reintroduce Actionlike::iter and reimplement old behaviors.

#

If we keep using or_default for every query like this, it might affect performance a bit. In the old version, because all action states were initialized, we avoided an extra layer of Option

#

@karmic monolith I'm curious about your thoughts on this

topaz dome
#

ohh..

candid vigil
#

Before LWIM 0.12, the internal ActionState was a Vec with initialized states of all Actionlikes

#

However, in the new version, Actionlike::iter was removed, and ActionState now uses a HashMap. This means that if an Action has never been updated by the internal system, its State will always be None if you just fetch it from action_data or action_data_mut

karmic monolith
topaz dome
candid vigil
#

Do we need to rename action_data and action_data_mut, or improve their documentation? Adding just "if populated" doesn't seem very clear

candid vigil
#

I'm unsure whether to use Vec2 or DualAxisData in #491

#

in trait refactoring

karmic monolith
candid vigil
#

There's one more thing I'm not quite sure about, and if it's convenient (since it's probably still early morning on your end)

#

Is it allowed to use &World in Bevy like this?

#
impl UserInput for KeyCode {
    fn input_kind(&self) -> InputKind {
        InputKind::Button
    }

    fn is_active(&self, world: &World) -> bool {
        world.resource::<ButtonInput<KeyCode>>().pressed(*self)
    }
}
#

If possible, I'd like to integrate some commonly used ActionStateDrivers, such as mouse position on the screen and in the world, into another version of UserInput that uses &mut World. Perhaps it could be called UserInputDriver?

#

Currently, I haven't found a better solution because generic traits can't be used, and associated type bounds will likely become stable in Rust 1.79, but that's a bit late

karmic monolith
candid vigil
#

Oh, then it's not a problem because I haven't used &World much in systems; mostly &mut World

candid vigil
#

Any progress on the review for #494? I've gathered most of the processor descriptions in the input_processing.rs documentation

#

But before moving forward with #490, I want to introduce a few new processors to get rid of some unnecessary VirtualDPads

#
/// Emulates an eight-way D-pad behavior, prioritizing stronger directional input.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[must_use]
pub struct EightWayDPad;

impl DualAxisProcessor for EightWayDPad {
    /// Returns a processed [`Vec2`] value representing one of the eight main compass directions.
    fn process(&self, input_value: Vec2) -> Vec2 {
        let Vec2 { x, y } = input_value;
        let x_abs = x.abs();
        let y_abs = y.abs();

        if x_abs > 2.0 * y_abs {
            Vec2::new(x.signum(), 0.0)
        } else if y_abs > 2.0 * x_abs {
            Vec2::new(0.0, y.signum())
        } else {
            input_value.signum()
        }
    }
}
#

This would help remove things like VirtualDPad::mouse_motion and simply add DualAxis::mouse_motion().with_processor(EightWayDPad)

#

VirtualDPad needs to read input for all four directions separately, calculate them individually, and then combine them

#

This leads to a lot of redundant calculations for axis inputs coming from the same event

candid vigil
#

After adding the new processors, the dual_axis_processor.rs file has become too large. Should we split it in #494 first?

#

With the new processors added, the entire file is 1960 lines long

#

The deadzone section comprises 360 lines of source code and 280 lines of test code, totaling 640 lines

#

The bounds section comprises 340 lines of source code and 180 lines of test code, totaling 520 lines

karmic monolith
#

I'll give you a review today

candid vigil
#

I don't know what's been going on lately, but every time I move items to another location, the cargo cache goes invalid and I can't compile. I have to clean it up each time

#

pushed #494 and updated the description

#

I've seen a 6-button D-pad variant in Unity recently, which corresponds to Vec3 inputs. I think we can introduce this in the future, but I'm not sure how to name it

#

Most input providers in Unity are strings in the code, which is very untyped and lacks type safety πŸ˜‚

candid vigil
#

I think I should rename the current 'with_processor' to 'replace_processor'

#

'with_processor' should be used to create a pipeline by adding the current processor and the given new processor

karmic monolith
#

I'll chew on names...

candid vigil
#

It's such a hassle

#

pushed the new changes

karmic monolith
# candid vigil pushed #494 and updated the description

Reviewed! Sorry for not being more proactive on this; I should have been following it more closely to avoid wasted work. Overall really well-made and a lovely abstraction, but there's a few architectural issues (pipelines, macros, typetag) that need to be addressed

candid vigil
#

Since the implementations generated by define_dual_axis_processor are enum variants (All, Separate(x, y), OnlyX, OnlyY), I feel like repeating code such as DualAxisDeadzone::All(AxisDeadzone::magnitude(0.2)) and DualAxisDeadzone::Separate(AxisDeadzone::magnitude(0.2), AxisDeadzone::magnitude(0.3)) would be more cumbersome

karmic monolith
candid vigil
#

to extend_dual

karmic monolith
#

Ah, I see

candid vigil
karmic monolith
#

Yeah, looking at the actual implementation I think I like it πŸ™‚

#

We can bikeshed the names maybe, but overall I think it makes sense

candid vigil
#

I can add a impl From<AxisProcessor> for DualAxisProcessor into All

karmic monolith
#

Yeah, I think that's probably still useful

#

But won't allow us to cut the methods

#

Since there's more than one sensible way to upcast it

#

(heading to sleep now fyi)

candid vigil
#

yeah, good night

candid vigil
#

When using structs with float fields, we cannot directly derive Eq

#

Here is a library created by a contributor to Bevy

#

But it's not as convenient as typetag, which only requires adding #[typetag::serde] above impl Trait for Type, whereas this one requires writing a lot of repetitive code manually

#

Another library that seems usable, but since almost no one uses it, it's still unclear whether it's viable

#

produces Vec3 values

#

bindings for up, down, left, right, forward, backward

#

This naming is a bit odd because D-pad stands for Directional Pad, so it's mostly referred to as a 4-way D-pad or an 8-way D-pad

#

up, down, left, right, up-left, up-right, down-left, down-right

#

LWIM's dpad is the 4-way version

#

but it produces an 8-way Vec2: -1, 0, 1 along each axis

candid vigil
#

I think DPad3D sounds better than 3Dpad because it should be a three-dimensional directional pad rather than a three-direction pad

#

I added a Digital processor that quantizes values into -1, 0, 1 for single-axis and dual-axis inputs in an upcoming PR

#

So the current VirtualDPad for axial inputs like mouse motion and sticks can be replaced with DualAxis::mouse_motion().with_processor(DualAxisDigital)

#

After trait refactoring, there will have MouseMotion::default() and MouseMotion::digital()

#

Inputs are only used in the InputMap, in actual logic, we deal with Actions and ActionState<Action>

#

For example, in a game I'm working on, we use Edge Pan and WASD to scroll the map

#

However, Edge Pan requires multiplying by 2.0 in its implementation to match the default Vec2 input values of VirtualDPad::WASD

#

This is because Unity seems to use this approach, even using strings in the code

#

Ah, this generic approach is also okay, but it can be quite complex

#

We'll need a Context to store InputTypeRegistry, then store all input values, and retrieve them by type. This replicates the implementation of Bevy's Query<Param>

#

Rust isn't as straightforward as C#. Both Bevy and Axum have to do it this way

#

Axum calls it the Extractor pattern. I haven't seen how Bevy refers to it in its implementation yet, but the approach is similar. It's mentioned in this release log

#

This library appears to be a simple implementation

#

However, because LWIM will eventually be upstreamed into Bevy itself, external dependencies need to be carefully considered

#

Moreover, Bevy has similar implementations internally, so perhaps they can be reused

midnight bramble
#

i can't even begin to imagine how to do it all, so it's all in your hands anyway

candid vigil
#

Ah, theoretically, it should be one way to implement Dependency Injection, and there should be many places to reference for guidance

midnight bramble
#

πŸ€·β€β™‚οΈ take your time to think it over, it should just be simple and failproof from both player and dev sides besides the initial setup

candid vigil
#

yeah, but it may not catch up with LWIM 0.14, as Bevy 0.14 might be released by the end of May

#

I think the api could be fn value<T: InputValueType>(&self) -> Option<T>

#

or Result

#

how about: if let Some(value) = action_state.value<Vec2>(&Move) { let dir: Vec2 = value; }

#

dir is just used to hint the type

#

I think zeros don't need to be handled, right? How should we handle them? For movement, it should simply mean not moving

#

Or input.Default.Move.performed += action_state.value<Vec2>().unwrap_or_default()

#

It's a bit tricky, but take a look at these examples

#

For continuous input values, you need to deal with events

#

LWIM currently handles them like this

#

CheatBook is outdated, but this part hasn't changed much

#

That's why LWIM exists; you only need to

let input_map = InputMap::new([
  (Move, MouseMotion),
  (Move, LeftStick),
])
let direction: Vec2 = action_state.axis_pair(&Move).unwrap().xy();
candid vigil
#

I'm on board

#

Oh yeah, I forgot

#

But in 0.13, it's still called Direction2/3d

#

directions are just normalized vectors

#

Some people might find it useful, but I'm not sure. LWIM also doesn't provide direct Dir2 input data, so if they need it, they'll have to manually convert it

#

LWIM's directions and rotations are only used in there

#

And this structure, DualAxisData, is just a wrapped Vec2, so my plan is to remove it in the next step

#

Rotation now is only used in orientation.rs (the module itself), examples, and this struct

#

it merged

#

LWIM's Direction has eight compass direction consts, but his only four

#

Rotation is clockwise/counterclockwise vs only counterclockwise

#

Other than that, there doesn't seem to be much difference, and it doesn't affect usage

candid vigil
#

infallible action_data seems like impossible

#

Because it only takes &self, inserting a default ActionData needs a mutable reference

#

But if we're just using unwrap_or_default() inside, we don't really need to make it mutable

karmic monolith
candid vigil
#

Serde trait objects on WASM have too many problems

#

On the one hand, using libs like serde_tagged is much lighter than typetag, but it also requires the use of #[ctor] to add code segments that run before main() because #[wasm_bingen(start)] can only appear once in entire applicaiton

#

This makes it impossible to pre-register all possible deserialization functions on WASM

#

On the other hand, it is possible to use App::register_typetag like bevy_serde_project

#

But this lib currently only supports deserialization of Box<dyn Trait>, while Option<Box<dyn Trait>> and Vec<Box<dyn Trait>> used in #494 are not supported

karmic monolith
#

Ugh 😦

candid vigil
#

This works, but I'm not sure if register_processor() should be a normal pub fn or an extension function for App

karmic monolith
#

You may also be able to get away with reusing Bevy's own type registry

placid furnace
#

I had an issue with the actions not updating when using a custom schedule (like at all, and only intermittently), but I managed to fix it by adding in a update_action_state before the system I read actions in, and it seems to be working quite well now.
Is there something I should be aware of when doing this? I don't care about the just_* functionality, as I post-process that myself, so I'm not calling tick_action_state, but I'm not sure if that's all it does or not.
Do you think should work okay?

candid vigil
#
pub type TypeRegistry = BTreeMap<&'static str, BoxFnSeed<Box<dyn AxisProcessor>>>;

static mut PROCESSOR_REGISTRY: RwLock<TypeRegistry> = RwLock::new(TypeRegistry::new());

fn register_processor(
    typetag: &'static str,
    func: impl FnSeed<Box<dyn AxisProcessor>> + Sync + 'static,
) {
    let mut registry = unsafe { PROCESSOR_REGISTRY.write().unwrap() };
    registry.insert(typetag, BoxFnSeed::new(func));
}

impl<'de> serde::Deserialize<'de> for Box<dyn AxisProcessor> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let registry = unsafe { PROCESSOR_REGISTRY.read().unwrap() };
        serde_tagged::de::external::deserialize(deserializer, &*registry)
    }
}
#

BoxFnSeed is the deserializer function for boxed dyn trait objects using the actual implementer's deserializer

#
impl Foo {
    fn deserialize_erased(
        de: &mut dyn erased_serde::Deserializer<'_>,
    ) -> Result<Box<dyn AxisProcessor>, erased_serde::Error> {
        Ok(Box::new(Self::deserialize(de)?))
    }
}

register_processor("Foo", Foo::deserialize_erased);
candid vigil
#

Simplified

trait Register<'de, T: serde::Deserialize<'de>> {
    fn register(registry: &mut RwLockWriteGuard<TypeRegistry<T>>);
}

impl<'de> Register<'de, Box<dyn AxisProcessor>> for Foo {
    fn register(registry: &mut RwLockWriteGuard<TypeRegistry<Box<dyn AxisProcessor>>>) {
        registry.insert("Foo", BoxFnSeed::new(Foo::deserialize_erased));
    }
}

fn register_processor<'de, T: Register<'de, Box<dyn AxisProcessor>>>() {
    let mut registry = unsafe { PROCESSOR_REGISTRY.write().unwrap() };
    T::register(&mut registry);
}

register_processor::<Foo>();
#

May I write a procedural macro to simplify the trait impls?

karmic monolith
candid vigil
#

Code seems like ready, but I should add tests for them

candid vigil
#

Pushed

candid vigil
#

I'll be quite busy next week

#

I can complete the works for #494 promptly, but other tasks like #490 may have to wait until two weeks from now

karmic monolith
#

Sounds good, thanks for the heads up πŸ™‚

candid vigil
#

Typetag-like stuffs are just serialization and deserializations for trait objects

#

Even if we don't use them in the processors and switch back to the original single struct implementation, the refactoring of UserInput still needs them

#

#346 Take this as an example, it can implement Trait Object Serialization, but Trait Object Deserialization can only be done in this way.

karmic monolith
#

Ugh. Right: we need to store a Box<dyn Buttonlike> or whatever in our input maps

#

And serialization and deserialization of input maps is non-negotiable

#

Okay. I think let's keep it, remove processors and get this merged?

candid vigil
#

Uhhh

#

I'm not sure which processors you mean. Can you be more specific?

karmic monolith
#

Oh sorry!

#

I meant pipelines πŸ™‚

candid vigil
#

All processors and Box<dyn Processor> are serde now

karmic monolith
#

Yep, my apologies

#

Pipelines appear to just be a performance optimization, correct?

#

I'd rather split that into its own PR

candid vigil
#

The old macro-optimized versions have been deleted in the PR. Now there is only the version that wraps Vec<Box<dyn Processor>>

karmic monolith
candid vigil
#

But I think the processing steps should not exceed five: Inversion, Sensitivity, Digital (which converts the input to -1,0,1, and will be implemented in a new PR), ValueBounds, and DeadZone

#

Therefore, there should not be any significant performance issues with the non-inline version either

#

Recently, I've implemented a new approach for handling UserInput.

pub enum InputKind {
    Complex,
    Button,
    Axis,
    DualAxis,
}

pub struct UserInputData {
    pub(crate) kind: InputKind,
    pub(crate) is_active: bool,
    pub(crate) value: f32,
    pub(crate) axis_pair: Option<Vec2>,
}

impl UserInputData {
    pub fn button(pressed: bool) -> Self {
        Self {
            kind: InputKind::Button,
            is_active: pressed,
            value: f32::from(pressed),
            axis_pair: None,
        }
    }

    pub fn axis(value: f32) -> Self {
        Self {
            kind: InputKind::Axis,
            is_active: value != 0.0,
            value,
            axis_pair: None,
        }
    }

    pub fn dual_axis(value: Vec2) -> Self {
        Self {
            kind: InputKind::DualAxis,
            is_active: value != Vec2::ZERO,
            value: value.length(),
            axis_pair: Some(value),
        }
    }
}

/// A trait for defining a specific user input.
pub trait UserInput {
    fn fetch(&self, input_streams: &InputStreams) -> UserInputData;
}
#

For example

impl UserInput for KeyCode {
    fn fetch(&self, input_streams: &InputStreams) -> UserInputData {
        let pressed = input_streams.keycodes.is_some_and(|kb| kb.pressed(*self));
        UserInputData::button(pressed)
    }
}
#

This should significantly reduce the number of redundant retrievals for input values

#
pub struct MouseMotionInput(Option<Box<dyn DualAxisProcessor>>);

impl UserInput for MouseMotionInput {
    fn fetch(&self, input_streams: &InputStreams) -> UserInputData {
        let mut value = Vec2::ZERO;
        for event in &input_streams.mouse_motion {
            value += event.delta;
        }
        if let Some(processor) = &self.0 {
            value = processor.process(value);
        }
        UserInputData::dual_axis(value)
    }
}
#

For the refactorings in #490, I'm not quite sure how to name the new input types

#

Many good names have been taken by Bevy, such as MouseMotion and Axis

#

So my current naming scheme is to add an *Input suffix, in line with ButtonInput and KeyboardInput

#

But, for single-axis inputs, such as the vertical mouse wheel, should we use MouseMotionInputVertical or MouseMotionInputY?

#

For #494, the current serde implementation is probably the simplest one, as the added dependencies are specifically for dyn trait objects

#

I've tried using InputProcessor<T> where T = f32 | Vec2 | Vec3, but there's currently no way in Rust to handle the deserialization of generic trait objects

#

This is because we can implement different traits, and deserialization cannot determine which specific trait to use, so I have to split it into concrete traits

#

Alternatively, we could create TypeTagRegistries for all possible input value types. However, this would be quite cumbersome, and it would also introduce potential issues with AsAny.

karmic monolith
candid vigil
#

Let me explain why TypeTag. It converts all trait objects into Rust's tagged enum format for Serde

#

For instance, given Foo(Vec2::new[1.0, 2.0]). After type tagging, it becomes equivalent to JSON {"Foo": [1.0, 2.0]}

karmic monolith
#

Right, I gathered that much. Does it work with user-supplied types that implement the trait?

candid vigil
#

yeah, I added register_*_processor as extenstion methods for App

karmic monolith
#

It seems surprising to me that you could generate an enum after those had been added to the code, rather than solely in the first-party library level πŸ€”

#

I definitely trust that this works, just trying to build up a better intuition

#

How can calling runtime methods like register_processor change the generated code?

#

Does it parse the AST and check for invocations?

candid vigil
#

nope, just add the Processor's type tag and a corresponding deserializer into the TypeTagRegistry, and call register_type for them

#

e.g. register_type::<AxisInverted>()

candid vigil
#

For example, many games have completely different dead zone implementations

karmic monolith
#

This is one of my core goals here

candid vigil
candid vigil
#

The gray area is the input switching zone, which is what's required in #505

karmic monolith
#

I wonder if we could procedurally generate these diagrams πŸ€”

candid vigil
#

Yeah, I suggested at the beginning of the month that we could create a visual example

#

@midnight bramble also said that he could add these to the GUI input manager he's working on

restive knoll
#

The graphs feel flawed ... They don't tell you how the green area is mapped πŸ€”

#

I feel like it needs those lines you see in 2D SDF examples so you can see how they scale ... Say make every other 0.1 value a differen color

candid vigil
#

Yeah

#

this original post seems to have been around for a long time, and many old games don't have scaled dead zones

#

I'm also quite unsure about the purpose of those oddly-shaped deadzone shapes

#

For now, CrossDeadzone and CircleDeadzone seem to suffice for most cases

#

One limits the value range along an axis, while the other limits the value's magnitude

#

On the other hand, I currently call those dual-axis ranges 'DualAxis* ' because the dual-axis version of AxisBounds is clearly a rectangle, while the dual-axis version of AxisDeadzone is a cross

#

So, for consistency, I call them DualAxis* ranges, since they're just the dual-axis versions of Axis* and I've added doc aliases

restive knoll
#

The spikes pointing towards the center makes some sense, it just snaps the angle if its close

#

A lot of them also just look like poorly implemented circle math πŸ˜‚

candid vigil
#

Ah, it's mainly because I don't often check for information in English game communities (I'm a Chinese player). I couldn't find the specific details about them this month

restive knoll
#

Deadzones are generally very overlooked by players, it's the kind of thing that makes people say "these controls suck" but not understand why ferris_sob

karmic monolith
#

@candid vigil 494 is merged!

#

Thank you so much for your hard work and patience here πŸ™‚

candid vigil
candid vigil
restive knoll
#

It's this shape that I'm most confused about πŸ˜‚

karmic monolith
candid vigil
#

how about this πŸ˜‚

karmic monolith
#

It's also much easier to iterate on some of the other work with this merged πŸ™‚

#

Since the diff is so large

restive knoll
candid vigil
#

Many of the settings are truly bizarre and incredibly fun, but I can't fathom how the code for them was written

karmic monolith
candid vigil
#

Ahh, I found an unused dependency that I forgot to removeπŸ˜‚

#

I'll create a cleanup PR to remove it and this

restive knoll
#

Hmmm ... I wonder if disabling components could at some point be useful as a more obvious version of having a component-level ToggleActions πŸ€”

candid vigil
#

Or should I split them into two separate PRs?

candid vigil
#

But out-of-date

karmic monolith
restive knoll
candid vigil
#

As per the changelog: Version 0.3: InputManagerPlugin::run_in_state has been replaced with the ToggleActions<A: Actionlike> resource, which determines whether or not [ActionState] / [InputMap] pairs of type A are active

#

Alice might still remember the reason πŸ˜‚

restive knoll
#

I mean at least ToggleActions is an improvement over passing a state in which to run the systems πŸ˜‚

candid vigil
#

Oh, I was mistaken. once_cell was used, so the new PR is clearer

restive knoll
candid vigil
#

I haven't used it in a real-world environment yet, so I haven't encountered issues πŸ˜‚

candid vigil
# karmic monolith Awesome, thank you

I made action_state::ActionData and timing::Timing implement Copy, and I made action_data() return a copy of ActionData instead of &ActionData. That should be fine, right?

#

It seems there's no way to change this position from the internal HashMap::get() -> Option<&ActionData> to return &ActionData. unwrap_or_default() doesn't work for Option<&>

karmic monolith
karmic monolith
candid vigil
#

But action_data() takes a &self

#

I made it like this

#

or rename the latter to get_action_data

karmic monolith
karmic monolith
#

Although, I guess we could just leave it as is, and then tell people to unwrap_or_default it themselves :/

#

That's probably the most idiomatic

candid vigil
#

Revert everything and only improve the documentation?

#

But how should this documentation be written? The current "if populated" is definitely unclear

karmic monolith
karmic monolith
candid vigil
#

Pushed the PR and off to dreamland

icy arrow
#

I'm porting my game to bevy 0.13 and leafwing-input-manager (from 0.11 to 0.13.3) and getting this error:

error[E0277]: the trait bound `bevy::prelude::KeyCode: leafwing_input_manager::Actionlike` is not satisfied
   --> crates/ui/src/lib.rs:115:19
    |
115 |   let input_map = InputMap::new([(KeyCode::Escape, UIAction::Esc)]).build();
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `leafwing_input_manager::Actionlike` is not implemented for `bevy::prelude::KeyCode`

how do I solve this? is there Actionline implementation for KeyCode?

candid vigil
#

InputMap::new([(UIAction::Esc, KeyCode::Escape)])

#

InputMap is a HashMap<Action, Vec<Input>> now

tame current
#

Is there a way to use an InputMap directly without the Action abstraction? e.g. feed it an Res<ButtonInput<T>> and check if the input is currently pressed.

karmic monolith
#

Although maybe I don't understand what you're after: that's not very useful at all without any actions

#

and if you just want to check if a ButtonInput is pressed, just call pressed on it directly

tame current
#

I think I miswrote what I needed there, but I wanted to see if I can re-use the UserInput abstraction without Action (I like the key modifier abstraction). I sometimes just want to do something quick and dirty where Action may be a bit too much.

candid vigil
#
use std::any::{TypeId, Any};

#[derive(Clone, Copy, Debug)]
pub struct Vec2 {
    x: f32,
    y: f32,
}

pub struct RawInputData {
    active: bool,
    value: f32,
    value2d: Option<Vec2>,
}

impl RawInputData {
    pub const fn is_active(&self) -> bool {
        self.active
    }

    #[inline(never)]
    pub fn get_value<V: Copy + 'static>(&self) -> Option<V> {
        let type_id = TypeId::of::<V>();
        
        if type_id == TypeId::of::<f32>() {
            let any: &dyn Any = &self.value;
            return any.downcast_ref::<V>().copied()
        }
        
        if let Some(value2d) = self.value2d {
            if type_id == TypeId::of::<Vec2>() {
                let any: &dyn Any = &value2d;
                return any.downcast_ref::<V>().copied()
            }
        }
        
        None
    }
}

pub fn foo(data: RawInputData) {
    println!("{:?}", data.get_value::<f32>());
    println!("{:?}", data.get_value::<Vec2>());
}

fn main() {
    foo(RawInputData {
        active: false,
        value: 2.5,
        value2d: Some(Vec2 {
            x: 2.8,
            y: 3.4,
        }),
    })
}
#

this works

#

And the compiled code is very well optimized.

#

just a context that stores input data from a single user input

#

But this is only for internal data transfer and not for public use. I think this implementation is the simplest, it can handle all possible types of input data (f32, Vec2, Vec3, Entity, etc.) in just a few lines. In the future, it will still be ActionState::get_value::<T>()

#

An enum is a closed set of types, with an arbitrary number of related properties.
A trait is a closed set of properties, with an arbitrary number of related types.

#

The current god enum UserInput prevents users from designing and using their own input types

#

For example, what about clicking on an object with the mouse or touching it and getting its EntityID?

#

The ultimate goal is to deduplicate the common input handling for Actions

#
fn update_cursor_state_from_window(
    window_query: Query<(&Window, &ActionStateDriver<BoxMovement>)>,
    mut action_state_query: Query<&mut ActionState<BoxMovement>>,
) {
    // Update each action state with the mouse position from the window
    // by using the referenced entities in ActionStateDriver and the stored action as
    // a key into the action data
    for (window, driver) in window_query.iter() {
        for entity in driver.targets.iter() {
            let mut action_state = action_state_query
                .get_mut(*entity)
                .expect("Entity does not exist, or does not have an `ActionState` component");

            if let Some(val) = window.cursor_position() {
                action_state
                    .action_data_mut_or_default(&driver.action)
                    .axis_pair = Some(DualAxisData::from_xy(val));
            }
        }
    }
}
#

This is how current LWIM users (not the built-in LWIM functionality) handle mouse clicks on the screen and trigger corresponding Actions

#

When interacting with other components in the game, users need to repeat a lot of these code. I think that many common methods can be extracted and converted into built-in methods

candid vigil
#

I think UI should also be a common way to trigger Actions. This will lead to many custom UserInputs

candid vigil
#
pub trait InputProcessor {
    type ValueType;
    fn process(&self, input_value: Self::ValueType) -> Self::ValueType;
}

#[derive(Clone, Copy)]
pub struct AxisInverted;

impl InputProcessor for AxisInverted {
    type ValueType = f32;

    fn process(&self, input_value: Self::ValueType) -> Self::ValueType {
        -input_value
    }
}

#[derive(Clone, Copy)]
pub struct AxisSensitivity(pub f32);

impl InputProcessor for AxisSensitivity {
    type ValueType = f32;

    fn process(&self, input_value: Self::ValueType) -> Self::ValueType {
        self.0 * input_value
    }
}

impl <T, P1: InputProcessor<ValueType = T>, P2: InputProcessor<ValueType = T>> InputProcessor for (P1, P2) {
    type ValueType = T;
    
    fn process(&self, input_value: Self::ValueType) -> Self::ValueType {
        self.1.process(self.0.process(input_value))
    }
}

pub fn main() {
    let processor = (AxisInverted, AxisSensitivity(1.5));
    let processor = (processor, AxisSensitivity(2.5));
    let processor: Box<dyn InputProcessor<ValueType = f32>> = Box::new(processor);
    println!("{}", processor.process(3.5));
}
#

This should perform better than the current Vec<Box<dyn Processor>>-based ProcessingPipelines

#

because it can be inlined

#
#

Then, implementing the UserInput trait for <U: UserInput, P: InputProcessor> (U, P) is all that's needed

#

@karmic monolith

karmic monolith
candid vigil
candid vigil
#

but there is an issue when integrating into the current main branch

#

because it is also an InputProcessor

#

then there is no way to distinguish whether the processor stored in the current UserInput is a regular processor (such as sensitivity and dead zone) or a Sequence

#

This is because the latter uses generics, so TypeId and Any::downcast cannot be used

#
DualAxis::mouse_motion().processor(
   // The first processor is a circular deadzone.
   // The next processor doubles inputs normalized by the deadzone.
   DualAxisSequentialProcessor::new(CircleDeadZone::new(0.1), DualAxisSensitivity::all(2.0))
       // The next processor inverts inputs along both axes.
       .with(DualAxisInverted::ALL),
),
#

It's a bit counterintuitive now

karmic monolith
candid vigil
#

this fine

#

But now there is a new issue, I forgot that generic trait objects cannot be deserialized.

#

that is, we cannot register_axis_processor::<AxisSequentialProcessor<_, _>>()

candid vigil
#
pub enum AxisProcessor {
    Pipeline(Vec<AxisProcessor>),
    Inverted,
    Sensitivity(f32),
    Custom(Box<dyn DynAxisProcessor>),
}

impl AxisProcessor {
    pub fn process(&self, input_value: f32) -> f32 {
        match self {
            Self::Pipeline(sequence) => {
                sequence.iter().fold(input_value, |value, next| next.process(value))
            },
            Self::Inverted => -input_value,
            Self::Sensitivity(sensitivity) => sensitivity * input_value,
            Self::Custom(processor) => processor.process(input_value),
        }
    }
}

pub trait DynAxisProcessor {
    fn process(&self, input_value: f32) -> f32;
}
#

I guess this is the only way

candid vigil
#
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum DualAxisProcessor {
    /// A wrapper around [`DualAxisInverted`] to represent inversion.
    Inverted(DualAxisInverted),

    /// A wrapper around [`DualAxisSensitivity`] to represent sensitivity.
    Sensitivity(DualAxisSensitivity),

    /// A wrapper around [`DualAxisBounds`] to represent value bounds.
    ValueBounds(DualAxisBounds),

    /// A wrapper around [`DualAxisExclusion`] to represent unscaled deadzone.
    Exclusion(DualAxisExclusion),

    /// A wrapper around [`DualAxisDeadZone`] to represent scaled deadzone.
    DeadZone(DualAxisDeadZone),

    /// A wrapper around [`CircleBounds`] to represent circular value bounds.
    CircleBounds(CircleBounds),

    /// A wrapper around [`CircleExclusion`] to represent unscaled deadzone.
    CircleExclusion(CircleExclusion),

    /// A wrapper around [`CircleDeadZone`] to represent scaled deadzone.
    CircleDeadZone(CircleDeadZone),

    /// Processes input values sequentially through a [`Vec`] containing [`DualAxisProcessor`]s.
    Pipeline(Vec<DualAxisProcessor>),

    /// A user-defined processor that implements [`CustomDualAxisProcessor`].
    Custom(Box<dyn CustomDualAxisProcessor>),
}
#
error[E0275]: overflow evaluating the requirement `std::vec::Vec<input_processing::dual_axis::DualAxisProcessor>: bevy::prelude::FromReflect`
   --> src\user_input.rs:232:45
    |
232 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
    |                                             ^^^^^^^
    |
note: required for `input_processing::dual_axis::DualAxisProcessor` to implement `bevy::prelude::FromReflect`
   --> src\input_processing\dual_axis\mod.rs:20:45
    |
20  | #[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
    |                                             ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro
21  | pub enum DualAxisProcessor {
    |          ^^^^^^^^^^^^^^^^^
    = note: 2 redundant requirements hidden
    = note: required for `axislike::DualAxis` to implement `bevy::prelude::FromReflect`
    = help: see issue #48214
    = note: this error originates in the derive macro `Reflect` (in Nightly builds, run with -Z macro-backtrace for more info)
help: add `#![feature(trivial_bounds)]` to the crate attributes to enable
   --> src/lib.rs:5:1
    |
5   + #![feature(trivial_bounds)]
    |

For more information about this error, try `rustc --explain E0275`.
#

It's a bit odd, but I can only change the implementation to a form like ((First, Second), Third)

#

This approach is slightly faster than using a Vec; on my computer, it's approximately 1.2x to 1.3x faster when comparing the speed of 2 to 10 processors

#
use std::hint::black_box;

const N: usize = 300;
#[derive(Clone)]
pub enum AxisProcessor {
    Inverted,

    Sensitivity(f32),

    Sequential(Box<AxisProcessor>, Box<AxisProcessor>),

    Sequence(Vec<AxisProcessor>),
}

impl AxisProcessor {
    /// Computes the result by processing the `input_value`.
    #[must_use]
    #[inline]
    pub fn process(&self, input_value: f32) -> f32 {
        match self {
            Self::Inverted => -input_value,
            Self::Sensitivity(sensitivity) => sensitivity * input_value,
            Self::Sequential(current, next) => next.process(current.process(input_value)),
            Self::Sequence(sequence) => sequence
                .iter()
                .fold(input_value, |value, next| next.process(value)),
        }
    }

    /// Appends the given `next_processor` as the next processing step.
    #[inline]
    pub fn with_processor(self, next_processor: impl Into<AxisProcessor>) -> Self {
        Self::Sequential(Box::new(self), Box::new(next_processor.into()))
    }
}

fn main() {
    let processor = AxisProcessor::Inverted
        .with_processor(AxisProcessor::Sensitivity(1.5))
        .with_processor(AxisProcessor::Sensitivity(5.0))
        .with_processor(AxisProcessor::Sensitivity(-3.0));
    println!(
        "With: {}",
        easybench::bench(|| {
            let mut sum = 0.0;
            for i in 0..N {
                sum += black_box(processor.process(i as f32));
            }
            sum
        })
    );

    let processor = AxisProcessor::Sequence(vec![
        AxisProcessor::Inverted,
        AxisProcessor::Sensitivity(1.5),
        AxisProcessor::Sensitivity(5.0),
        AxisProcessor::Sensitivity(-3.0),
    ]);
    println!(
        "Vec: {}",
        easybench::bench(|| {
            let mut sum = 0.0;
            for i in 0..N {
                sum += black_box(processor.process(i as f32));
            }
            sum
        })
    );
}
candid vigil
#

pushed #511 PR

candid vigil
#

Pipeline(Vec<Arc<AxisProcessor>>) resolved [E0275]

candid vigil
#

Unfortunately, due to compilation error E0117, it's not possible to impl serde for Arc<dyn T>

#

Otherwise, it might have been possible to eliminate the manual impl of reflection for Arc<dyn T>

candid vigil
#

I feel like there are many areas that could be rewritten better, but it would require too many breaking changes

#

For example, using a HashMap<Box<dyn Actionlike>, Vec<Box<dyn UserInput>>> in InputMap and a similar approach in ActionState could potentially eliminate all generics and require only one addition for InputManagerPlugin into App

#

Additionally, I plan to streamline the functions within InputStream so that it only has something like a get_data method

#
pub struct UserInputData {
    pub(crate) is_active: bool,
    pub(crate) value: f32,
    pub(crate) axis_pair: Option<Vec2>,
}
#

I'll probably start a new repo next week, write a rough draft of the completely new version, and then figure out how to gradually transition to LWIM

karmic monolith
candid vigil
#

I still find the behavior of Chord somewhat inconsistent

#

Its value is the sum of all single-axis inputs and ignores the magnitude of dual-axis inputs

#

However, its axis_pair only retrieves the value of the first dual-axis input

river cliff
#

That was an oversight on my part, sorry. I did not see it was already implemented for axis_pair and should've followed that. For value, the issue I was looking to fix was #380. I believe the reasoning I had for adding together the values was by asking "what if there was more than one SingleAxis value in the chord?" So, if there was a chord with two SingleAxis inputs one with -1.0 and another 1.0, I thought the chord taking both into account made more sense.

GitHub

A straightforward stateful input manager for the Bevy game engine. - Issues Β· Leafwing-Studios/leafwing-input-manager

river cliff
#

Just noticed that the docs on value wasn't updated to explain this either.

candid vigil
#

Yeah, I understand this part

#

I'm currently breaking down UserInput and InputKind into trait implementations, so I'm thinking about what the behavior of Chord should be

#

Because I still need to implement Sequence after that

#

Chord's axis_pair doesn't consider the values of multiple dual-axis inputs, only taking the first one, and value also ignores the magnitude of all dual-axis inputs

umbral linden
#

Hi, I have a test where I do:

  • BevyInput's KeyCode Press
  • run Update() -> check that ActionState is JustPressed
  • BevyInput's KeyCode Releaes
  • run Update() -> check that ActionState is JustReleased
    but sometimes I notice that I need to run update() twice for my KeyCode::release to cause a ActionState::JustReleased

it's probably me, but i wonder if that's something that was seen before

umbral linden
#

Ah this might be because i'm running this in a test; and leafwing uses Time<Real> in tick_actions, which could be causing issues? not sure

candid vigil
#

@karmic monolith Now on my end, all the variants of enums like UserInput and InputKind have been converted into the UserInput trait impls

#

but I have a few questions

#

For example, do I also need to implement some of the methods that come with the old UserInput enum, such as len, is_empty, n_matching, and raw_inputs?

karmic monolith
#

We can re-evaluate the API after and see what people need and what makes sense

candid vigil
#

yeah, only testing and documentation are left, so it should be completed in a day or two

karmic monolith
#

Awesome ❀️

#

I'll review promptly: bandwidth is doing much better and we're looking good to make the next Bevy release

#

I think we'll actually ship two versions of LWIM for that: a trivial "just upgrade Bevy" release, and the refactor release

#

To make it easier for people to migrate

candid vigil
#

agree

#

and if time allows, I may also introduce some breaking changes

#

For example

#

to implement InputSequence, we would need the InputStreams to track input with ticks

#

but before that, I think we can break down InputStreams into smaller Resources similar to Bevy's ButtonInput and Axis

#

And their internal data can be fetched on-demand, and if a device (like mouse) is not in the InputMap, we can skip fetching its data

#

Even ActionData can also become lazily fetched because even if I only need the input's value or axis_pair, it still calculates and stores whether it is_pressed every frame

candid vigil
#

Is there any recommended way by Bevy to implement a Resource that contains lazy data?

karmic monolith
#

Or as in "added after startup"

candid vigil
#

the former

karmic monolith
#

So, for simple and light things I would store a copy of needed the data in the resource, and then just have getter methods that do the crunchy calculation

#

But if you don't want to clone it and instead rely on a different source of truth, a custom system param that fetches the required data (including any local cache in a private resource) is better

candid vigil
#

but some inputs can impose a significant computational burden

#

such as converting mouse coordinates in world space and screen

#

I want to implement these as UserInput as well, but it's clear that collecting this data not on demand would impact performance

#

Using something like once_cell::Lazy makes it simple to achieve that; just stores a Lazy::new(|| heavy_computation())

#

but I'm curious if Bevy has any officially recommended approach for this

karmic monolith
#

Hmm, nothing beyond what I recommended above

#

This isn't a pattern I've seen in the engine itself yet

#

You could also do a handle-style reference counting approach

#

To count the number of users that care

#

And then compute it if at least one user cares

candid vigil
#

Okay, I'll explore if there's any simple way later

karmic monolith
#

Yep πŸ™‚

#

Like always, working first, then benchmarks, then try and speed things up :p

candid vigil
#

I'm unsure about the clash check for mouse input

#

For instance, why doesn't MouseMotion (the dual-axis version) clash with MouseMotionAxisType (the single-axis version) and MouseMotionDirection (the button-like version)?

candid vigil
#

However, for mouse input, I didn't implement VirtualAxis and VirtualDPad

#

Instead, I used an AxisInputMode internally in MouseMotion, MouseMotionAxis, MouseWheel, and MouseWheelAxis to indicate whether they should be continuous analog inputs or discrete digital inputs (-1, 0, 1)

#

So, MouseMotionDirection and MouseWheelDirection seem less useful now

#

Does anyone actually use a specific MouseDirection instead of MouseAxis corresponding to an action?

candid vigil
#

And does anyone actually use MouseButtons in VirtualAxis and VirtualDPad?

karmic monolith