Make Gilrs a normal resource on non-Wasm targets (#12092)

# Objective
Partially address #888. Gilrs is initialized on a separate thread, and
thus conditionally implements `Send`, and all platforms other than Wasm.
This means the `NonSend` resource constraint is likely too conservative.

## Solution
Relax the requirement, and conditionally derive Resource on a wrapper
around it, using `SyncCell` to satisfy the `Sync` requirement on it.
This commit is contained in:
James Liu 2024-02-25 16:23:42 -08:00 committed by GitHub
parent ad5d790e9e
commit 3a1b9b98e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 26 deletions

View file

@ -1,6 +1,11 @@
use crate::converter::{convert_axis, convert_button, convert_gamepad_id}; use crate::{
converter::{convert_axis, convert_button, convert_gamepad_id},
Gilrs,
};
use bevy_ecs::event::EventWriter; use bevy_ecs::event::EventWriter;
use bevy_ecs::system::{NonSend, NonSendMut, Res, ResMut}; #[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::{Res, ResMut};
use bevy_input::gamepad::{ use bevy_input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent,
GamepadSettings, GamepadSettings,
@ -8,13 +13,14 @@ use bevy_input::gamepad::{
use bevy_input::gamepad::{GamepadEvent, GamepadInfo}; use bevy_input::gamepad::{GamepadEvent, GamepadInfo};
use bevy_input::prelude::{GamepadAxis, GamepadButton}; use bevy_input::prelude::{GamepadAxis, GamepadButton};
use bevy_input::Axis; use bevy_input::Axis;
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter, Gilrs}; use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
pub fn gilrs_event_startup_system( pub fn gilrs_event_startup_system(
gilrs: NonSend<Gilrs>, #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut connection_events: EventWriter<GamepadConnectionEvent>, mut connection_events: EventWriter<GamepadConnectionEvent>,
) { ) {
for (id, gamepad) in gilrs.gamepads() { for (id, gamepad) in gilrs.0.get().gamepads() {
let info = GamepadInfo { let info = GamepadInfo {
name: gamepad.name().into(), name: gamepad.name().into(),
}; };
@ -27,16 +33,15 @@ pub fn gilrs_event_startup_system(
} }
pub fn gilrs_event_system( pub fn gilrs_event_system(
mut gilrs: NonSendMut<Gilrs>, #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut events: EventWriter<GamepadEvent>, mut events: EventWriter<GamepadEvent>,
mut gamepad_buttons: ResMut<Axis<GamepadButton>>, mut gamepad_buttons: ResMut<Axis<GamepadButton>>,
gamepad_axis: Res<Axis<GamepadAxis>>, gamepad_axis: Res<Axis<GamepadAxis>>,
gamepad_settings: Res<GamepadSettings>, gamepad_settings: Res<GamepadSettings>,
) { ) {
while let Some(gilrs_event) = gilrs let gilrs = gilrs.0.get();
.next_event() while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) {
.filter_ev(&axis_dpad_to_button, &mut gilrs)
{
gilrs.update(&gilrs_event); gilrs.update(&gilrs_event);
let gamepad = convert_gamepad_id(gilrs_event.id); let gamepad = convert_gamepad_id(gilrs_event.id);

View file

@ -10,11 +10,14 @@ mod rumble;
use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate}; use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_input::InputSystem; use bevy_input::InputSystem;
use bevy_utils::tracing::error; use bevy_utils::{synccell::SyncCell, tracing::error};
use gilrs::GilrsBuilder; use gilrs::GilrsBuilder;
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use rumble::{play_gilrs_rumble, RunningRumbleEffects};
#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))]
pub(crate) struct Gilrs(pub SyncCell<gilrs::Gilrs>);
/// Plugin that provides gamepad handling to an [`App`]. /// Plugin that provides gamepad handling to an [`App`].
#[derive(Default)] #[derive(Default)]
pub struct GilrsPlugin; pub struct GilrsPlugin;
@ -31,8 +34,12 @@ impl Plugin for GilrsPlugin {
.build() .build()
{ {
Ok(gilrs) => { Ok(gilrs) => {
app.insert_non_send_resource(gilrs) #[cfg(target_arch = "wasm32")]
.init_non_send_resource::<RunningRumbleEffects>() app.insert_non_send_resource(Gilrs(SyncCell::new(gilrs)));
#[cfg(not(target_arch = "wasm32"))]
app.insert_resource(Gilrs(SyncCell::new(gilrs)));
app.init_resource::<RunningRumbleEffects>()
.add_systems(PreStartup, gilrs_event_startup_system) .add_systems(PreStartup, gilrs_event_startup_system)
.add_systems(PreUpdate, gilrs_event_system.before(InputSystem)) .add_systems(PreUpdate, gilrs_event_system.before(InputSystem))
.add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystem)); .add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystem));

View file

@ -1,15 +1,15 @@
//! Handle user specified rumble request events. //! Handle user specified rumble request events.
use bevy_ecs::{ use crate::Gilrs;
prelude::{EventReader, Res}, use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource};
system::NonSendMut, #[cfg(target_arch = "wasm32")]
}; use bevy_ecs::system::NonSendMut;
use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest};
use bevy_log::{debug, warn}; use bevy_log::{debug, warn};
use bevy_time::{Real, Time}; use bevy_time::{Real, Time};
use bevy_utils::{Duration, HashMap}; use bevy_utils::{synccell::SyncCell, Duration, HashMap};
use gilrs::{ use gilrs::{
ff::{self, BaseEffect, BaseEffectType, Repeat, Replay}, ff::{self, BaseEffect, BaseEffectType, Repeat, Replay},
GamepadId, Gilrs, GamepadId,
}; };
use thiserror::Error; use thiserror::Error;
@ -23,7 +23,7 @@ struct RunningRumble {
/// ///
/// Dropping it will cause the effect to stop /// Dropping it will cause the effect to stop
#[allow(dead_code)] #[allow(dead_code)]
effect: ff::Effect, effect: SyncCell<ff::Effect>,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -35,7 +35,7 @@ enum RumbleError {
} }
/// Contains the gilrs rumble effects that are currently running for each gamepad /// Contains the gilrs rumble effects that are currently running for each gamepad
#[derive(Default)] #[derive(Default, Resource)]
pub(crate) struct RunningRumbleEffects { pub(crate) struct RunningRumbleEffects {
/// If multiple rumbles are running at the same time, their resulting rumble /// If multiple rumbles are running at the same time, their resulting rumble
/// will be the saturated sum of their strengths up until [`u16::MAX`] /// will be the saturated sum of their strengths up until [`u16::MAX`]
@ -80,7 +80,7 @@ fn get_base_effects(
fn handle_rumble_request( fn handle_rumble_request(
running_rumbles: &mut RunningRumbleEffects, running_rumbles: &mut RunningRumbleEffects,
gilrs: &mut Gilrs, gilrs: &mut gilrs::Gilrs,
rumble: GamepadRumbleRequest, rumble: GamepadRumbleRequest,
current_time: Duration, current_time: Duration,
) -> Result<(), RumbleError> { ) -> Result<(), RumbleError> {
@ -113,7 +113,10 @@ fn handle_rumble_request(
let gamepad_rumbles = running_rumbles.rumbles.entry(gamepad_id).or_default(); let gamepad_rumbles = running_rumbles.rumbles.entry(gamepad_id).or_default();
let deadline = current_time + duration; let deadline = current_time + duration;
gamepad_rumbles.push(RunningRumble { deadline, effect }); gamepad_rumbles.push(RunningRumble {
deadline,
effect: SyncCell::new(effect),
});
} }
} }
@ -121,10 +124,12 @@ fn handle_rumble_request(
} }
pub(crate) fn play_gilrs_rumble( pub(crate) fn play_gilrs_rumble(
time: Res<Time<Real>>, time: Res<Time<Real>>,
mut gilrs: NonSendMut<Gilrs>, #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut<Gilrs>,
#[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut<Gilrs>,
mut requests: EventReader<GamepadRumbleRequest>, mut requests: EventReader<GamepadRumbleRequest>,
mut running_rumbles: NonSendMut<RunningRumbleEffects>, mut running_rumbles: ResMut<RunningRumbleEffects>,
) { ) {
let gilrs = gilrs.0.get();
let current_time = time.elapsed(); let current_time = time.elapsed();
// Remove outdated rumble effects. // Remove outdated rumble effects.
for rumbles in running_rumbles.rumbles.values_mut() { for rumbles in running_rumbles.rumbles.values_mut() {
@ -138,7 +143,7 @@ pub(crate) fn play_gilrs_rumble(
// Add new effects. // Add new effects.
for rumble in requests.read().cloned() { for rumble in requests.read().cloned() {
let gamepad = rumble.gamepad(); let gamepad = rumble.gamepad();
match handle_rumble_request(&mut running_rumbles, &mut gilrs, rumble, current_time) { match handle_rumble_request(&mut running_rumbles, gilrs, rumble, current_time) {
Ok(()) => {} Ok(()) => {}
Err(RumbleError::GilrsError(err)) => { Err(RumbleError::GilrsError(err)) => {
if let ff::Error::FfNotSupported(_) = err { if let ff::Error::FfNotSupported(_) = err {