-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Add gamepad rumble support to bevy_input #8398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 33 commits
0707bdc
8bfbfda
1628d42
a2f5fd9
6ca8654
34e6420
08e5690
7787e6a
55985d7
0b5615f
47dd5b3
dc75f63
d16c375
bac6ddd
c36af78
254be0f
7fed666
e54e6ef
d092d2a
90079b9
1f4e744
23668eb
fbc5283
e26fd82
ef0e248
fc92a5b
d455a2b
307eadc
380764a
71a09b6
9a0a616
714f2af
643b9a4
8d0ab03
52c7f4f
fc21d2f
677fd91
dcf8e2e
58825df
f9748db
ecd61ca
e07a55c
d83959a
388ef33
836218a
fa5ceb0
f5ed292
7978fae
438051f
f250fb8
4261821
103fceb
1829fc7
a87e486
673facd
990a262
98b26f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,146 @@ | ||||||
//! Handle user specified rumble request events. | ||||||
use bevy_ecs::{ | ||||||
prelude::{EventReader, Res}, | ||||||
system::NonSendMut, | ||||||
}; | ||||||
use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; | ||||||
use bevy_log::{debug, warn}; | ||||||
use bevy_time::Time; | ||||||
use bevy_utils::HashMap; | ||||||
use gilrs::{ | ||||||
ff::{self, BaseEffect, BaseEffectType}, | ||||||
GamepadId, Gilrs, | ||||||
}; | ||||||
|
||||||
use crate::converter::convert_gamepad_id; | ||||||
|
||||||
struct RunningRumble { | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
deadline: f32, | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// We use `effect.drop()` to interact with this, but rustc can't know | ||||||
// gilrs uses Drop as an API feature. | ||||||
#[allow(dead_code)] | ||||||
effect: ff::Effect, | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
enum RumbleError { | ||||||
GamepadNotFound, | ||||||
GilrsError(ff::Error), | ||||||
} | ||||||
impl From<ff::Error> for RumbleError { | ||||||
fn from(err: ff::Error) -> Self { | ||||||
RumbleError::GilrsError(err) | ||||||
} | ||||||
} | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
#[derive(Default)] | ||||||
pub(crate) struct RumblesManager { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doc strings please. This is used as a non-send resource below, but it's hard to figure out the intent of this at first glance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed it to |
||||||
rumbles: HashMap<GamepadId, Vec<RunningRumble>>, | ||||||
} | ||||||
|
||||||
fn to_gilrs_magnitude(ratio: f32) -> u16 { | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
(ratio * u16::MAX as f32) as u16 | ||||||
} | ||||||
|
||||||
fn get_base_effects( | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
GamepadRumbleIntensity { weak, strong }: GamepadRumbleIntensity, | ||||||
) -> Vec<ff::BaseEffect> { | ||||||
let mut effects = Vec::new(); | ||||||
if strong > 0. { | ||||||
effects.push(BaseEffect { | ||||||
kind: BaseEffectType::Strong { | ||||||
magnitude: to_gilrs_magnitude(strong), | ||||||
}, | ||||||
..Default::default() | ||||||
}); | ||||||
} | ||||||
if weak > 0. { | ||||||
effects.push(BaseEffect { | ||||||
kind: BaseEffectType::Strong { | ||||||
magnitude: to_gilrs_magnitude(weak), | ||||||
}, | ||||||
..Default::default() | ||||||
}); | ||||||
} | ||||||
effects | ||||||
} | ||||||
|
||||||
fn handle_rumble_request( | ||||||
manager: &mut RumblesManager, | ||||||
gilrs: &mut Gilrs, | ||||||
rumble: GamepadRumbleRequest, | ||||||
current_time: f32, | ||||||
) -> Result<(), RumbleError> { | ||||||
let gamepad = match rumble { | ||||||
GamepadRumbleRequest::Add { gamepad, .. } | GamepadRumbleRequest::Stop { gamepad } => { | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
gamepad | ||||||
} | ||||||
}; | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
let (gamepad_id, _) = gilrs | ||||||
.gamepads() | ||||||
.find(|(pad_id, _)| convert_gamepad_id(*pad_id) == gamepad) | ||||||
.ok_or(RumbleError::GamepadNotFound)?; | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
match rumble { | ||||||
GamepadRumbleRequest::Stop { .. } => { | ||||||
// `ff::Effect` uses RAII, dropping = deactivating | ||||||
manager.rumbles.remove(&gamepad_id); | ||||||
} | ||||||
GamepadRumbleRequest::Add { | ||||||
duration, | ||||||
intensity, | ||||||
.. | ||||||
} => { | ||||||
let deadline = current_time + duration.as_secs_f32(); | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
let mut effect_builder = ff::EffectBuilder::new(); | ||||||
|
||||||
for effect in get_base_effects(intensity) { | ||||||
effect_builder.add_effect(effect); | ||||||
} | ||||||
|
||||||
let effect = effect_builder.gamepads(&[gamepad_id]).finish(gilrs)?; | ||||||
effect.play()?; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. info sidenote: This API is so... hmm, different from what we settled on. The motivation behind it is laid out here https://gitlab.com/gilrs-project/gilrs/-/merge_requests/11. Nothing related to this code specifically, just FYI for future people. |
||||||
|
||||||
let gamepad_rumbles = manager.rumbles.entry(gamepad_id).or_default(); | ||||||
gamepad_rumbles.push(RunningRumble { deadline, effect }); | ||||||
} | ||||||
} | ||||||
|
||||||
Ok(()) | ||||||
} | ||||||
pub(crate) fn play_gilrs_rumble( | ||||||
time: Res<Time>, | ||||||
mut gilrs: NonSendMut<Gilrs>, | ||||||
mut requests: EventReader<GamepadRumbleRequest>, | ||||||
mut manager: NonSendMut<RumblesManager>, | ||||||
) { | ||||||
let current_time = time.elapsed_seconds(); | ||||||
// Remove outdated rumble effects. | ||||||
for (_gamepad, rumbles) in manager.rumbles.iter_mut() { | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// `ff::Effect` uses RAII, dropping = deactivating | ||||||
rumbles.retain(|RunningRumble { deadline, .. }| *deadline >= current_time); | ||||||
} | ||||||
manager | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
.rumbles | ||||||
.retain(|_gamepad, rumbles| !rumbles.is_empty()); | ||||||
|
||||||
// Add new effects. | ||||||
for rumble in requests.iter().cloned() { | ||||||
let gamepad = match rumble { | ||||||
GamepadRumbleRequest::Add { gamepad, .. } | GamepadRumbleRequest::Stop { gamepad } => { | ||||||
gamepad | ||||||
} | ||||||
}; | ||||||
johanhelsing marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
match handle_rumble_request(&mut manager, &mut gilrs, rumble, current_time) { | ||||||
Ok(()) => {} | ||||||
Err(RumbleError::GilrsError(err)) => { | ||||||
debug!( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think these errors are similar severity, and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made these warnings, but left force feedback not supported as a debug, since we currently don't have a way to query for force feedback support for a controller (avoid spamming warnings the user can't solve) |
||||||
"Tried to handle rumble request for {gamepad:?} but an error occurred: {err}" | ||||||
); | ||||||
} | ||||||
Err(RumbleError::GamepadNotFound) => { | ||||||
warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); | ||||||
} | ||||||
}; | ||||||
} | ||||||
} |
Uh oh!
There was an error while loading. Please reload this page.