Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_gilrs/src/lib.rs
9368 views
1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![forbid(unsafe_code)]
3
#![doc(
4
html_logo_url = "https://bevy.org/assets/icon.png",
5
html_favicon_url = "https://bevy.org/assets/icon.png"
6
)]
7
8
//! Systems and type definitions for gamepad handling in Bevy.
9
//!
10
//! This crate is built on top of [GilRs](gilrs), a library
11
//! that handles abstracting over platform-specific gamepad APIs.
12
13
mod converter;
14
mod gilrs_system;
15
mod rumble;
16
17
#[cfg(not(target_arch = "wasm32"))]
18
use bevy_platform::cell::SyncCell;
19
20
#[cfg(target_arch = "wasm32")]
21
use core::cell::RefCell;
22
23
use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate};
24
use bevy_ecs::entity::EntityHashMap;
25
use bevy_ecs::prelude::*;
26
use bevy_input::InputSystems;
27
use bevy_platform::collections::HashMap;
28
use gilrs::GilrsBuilder;
29
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
30
use rumble::{play_gilrs_rumble, RunningRumbleEffects};
31
use tracing::error;
32
33
#[cfg(target_arch = "wasm32")]
34
thread_local! {
35
/// Temporary storage of gilrs data to replace usage of `!Send` resources. This will be replaced with proper
36
/// storage of `!Send` data after issue #17667 is complete.
37
///
38
/// Using a `thread_local!` here relies on the fact that wasm32 can only be single threaded. Previously, we used a
39
/// `NonSendMut` parameter, which told Bevy that the system was `!Send`, but now with the removal of `!Send`
40
/// resource/system parameter usage, there is no internal guarantee that the system will run in only one thread, so
41
/// we need to rely on the platform to make such a guarantee.
42
pub static GILRS: RefCell<Option<gilrs::Gilrs>> = const { RefCell::new(None) };
43
}
44
45
#[derive(Resource)]
46
pub(crate) struct Gilrs {
47
#[cfg(not(target_arch = "wasm32"))]
48
cell: SyncCell<gilrs::Gilrs>,
49
}
50
51
impl Gilrs {
52
#[inline]
53
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {
54
#[cfg(target_arch = "wasm32")]
55
GILRS.with(|g| f(g.borrow_mut().as_mut().expect("GILRS was not initialized")));
56
#[cfg(not(target_arch = "wasm32"))]
57
f(self.cell.get());
58
}
59
}
60
61
/// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`].
62
#[derive(Debug, Default, Resource)]
63
pub(crate) struct GilrsGamepads {
64
/// Mapping of [`Entity`] to [`gilrs::GamepadId`].
65
pub(crate) entity_to_id: EntityHashMap<gilrs::GamepadId>,
66
/// Mapping of [`gilrs::GamepadId`] to [`Entity`].
67
pub(crate) id_to_entity: HashMap<gilrs::GamepadId, Entity>,
68
}
69
70
impl GilrsGamepads {
71
/// Returns the [`Entity`] assigned to a connected [`gilrs::GamepadId`].
72
pub fn get_entity(&self, gamepad_id: gilrs::GamepadId) -> Option<Entity> {
73
self.id_to_entity.get(&gamepad_id).copied()
74
}
75
76
/// Returns the [`gilrs::GamepadId`] assigned to a gamepad [`Entity`].
77
pub fn get_gamepad_id(&self, entity: Entity) -> Option<gilrs::GamepadId> {
78
self.entity_to_id.get(&entity).copied()
79
}
80
}
81
82
/// Plugin that provides gamepad handling to an [`App`].
83
#[derive(Default)]
84
pub struct GilrsPlugin;
85
86
/// Updates the running gamepad rumble effects.
87
#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
88
pub struct RumbleSystems;
89
90
impl Plugin for GilrsPlugin {
91
fn build(&self, app: &mut App) {
92
match GilrsBuilder::new()
93
.with_default_filters(false)
94
.set_update_state(false)
95
.build()
96
{
97
Ok(gilrs) => {
98
let g = Gilrs {
99
#[cfg(not(target_arch = "wasm32"))]
100
cell: SyncCell::new(gilrs),
101
};
102
#[cfg(target_arch = "wasm32")]
103
GILRS.with(|g| {
104
g.replace(Some(gilrs));
105
});
106
app.insert_resource(g);
107
app.init_resource::<GilrsGamepads>();
108
app.init_resource::<RunningRumbleEffects>()
109
.add_systems(PreStartup, gilrs_event_startup_system)
110
.add_systems(PreUpdate, gilrs_event_system.before(InputSystems))
111
.add_systems(PostUpdate, play_gilrs_rumble.in_set(RumbleSystems));
112
}
113
Err(err) => error!("Failed to start Gilrs. {}", err),
114
}
115
}
116
}
117
118
#[cfg(test)]
119
mod tests {
120
use super::*;
121
122
// Regression test for https://github.com/bevyengine/bevy/issues/17697
123
#[test]
124
fn world_is_truly_send() {
125
let mut app = App::new();
126
app.add_plugins(GilrsPlugin);
127
let world = core::mem::take(app.world_mut());
128
129
let handler = std::thread::spawn(move || {
130
drop(world);
131
});
132
133
handler.join().unwrap();
134
}
135
}
136
137