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::system::{NonSend, NonSendMut, Res, ResMut};
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::{Res, ResMut};
use bevy_input::gamepad::{
GamepadAxisChangedEvent, GamepadButtonChangedEvent, GamepadConnection, GamepadConnectionEvent,
GamepadSettings,
@ -8,13 +13,14 @@ use bevy_input::gamepad::{
use bevy_input::gamepad::{GamepadEvent, GamepadInfo};
use bevy_input::prelude::{GamepadAxis, GamepadButton};
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(
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>,
) {
for (id, gamepad) in gilrs.gamepads() {
for (id, gamepad) in gilrs.0.get().gamepads() {
let info = GamepadInfo {
name: gamepad.name().into(),
};
@ -27,16 +33,15 @@ pub fn gilrs_event_startup_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 gamepad_buttons: ResMut<Axis<GamepadButton>>,
gamepad_axis: Res<Axis<GamepadAxis>>,
gamepad_settings: Res<GamepadSettings>,
) {
while let Some(gilrs_event) = gilrs
.next_event()
.filter_ev(&axis_dpad_to_button, &mut gilrs)
{
let gilrs = gilrs.0.get();
while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) {
gilrs.update(&gilrs_event);
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_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_utils::tracing::error;
use bevy_utils::{synccell::SyncCell, tracing::error};
use gilrs::GilrsBuilder;
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
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`].
#[derive(Default)]
pub struct GilrsPlugin;
@ -31,8 +34,12 @@ impl Plugin for GilrsPlugin {
.build()
{
Ok(gilrs) => {
app.insert_non_send_resource(gilrs)
.init_non_send_resource::<RunningRumbleEffects>()
#[cfg(target_arch = "wasm32")]
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(PreUpdate, gilrs_event_system.before(InputSystem))
.add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystem));

View file

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