
# Objective Work for issue #17682 What's in this PR: * Removal of some `!Send` resources that Bevy uses internally * Replaces `!Send` resources with `thread_local!` static What this PR does not cover: * The ability to create `!Send` resources still exists * Tests that test `!Send` resources are present (and should not be removed until the ability to create `!Send` resources is removed) * The example `log_layers_ecs` still uses a `!Send` resource. In this example, removing the `!Send` resource results in the system that uses it running on a thread other than the main thread, which doesn't work with lazily initialized `thread_local!` static data. Removing this `!Send` resource will need to be deferred until the System API is extended to support configuring which thread the System runs on. Once an issue for this work is created, it will be mentioned in #17667 Once the System API is extended to allow control of which thread the System runs on, the rest of the `!Send` resources can be removed in a different PR.
116 lines
4.1 KiB
Rust
116 lines
4.1 KiB
Rust
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![forbid(unsafe_code)]
|
|
#![doc(
|
|
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
|
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
|
)]
|
|
|
|
//! Systems and type definitions for gamepad handling in Bevy.
|
|
//!
|
|
//! This crate is built on top of [GilRs](gilrs), a library
|
|
//! that handles abstracting over platform-specific gamepad APIs.
|
|
|
|
mod converter;
|
|
mod gilrs_system;
|
|
mod rumble;
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
use bevy_utils::synccell::SyncCell;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
use core::cell::RefCell;
|
|
|
|
use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate};
|
|
use bevy_ecs::entity::hash_map::EntityHashMap;
|
|
use bevy_ecs::prelude::*;
|
|
use bevy_input::InputSystem;
|
|
use bevy_platform_support::collections::HashMap;
|
|
use gilrs::GilrsBuilder;
|
|
use gilrs_system::{gilrs_event_startup_system, gilrs_event_system};
|
|
use rumble::{play_gilrs_rumble, RunningRumbleEffects};
|
|
use tracing::error;
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
thread_local! {
|
|
/// Temporary storage of gilrs data to replace usage of `!Send` resources. This will be replaced with proper
|
|
/// storage of `!Send` data after issue #17667 is complete.
|
|
///
|
|
/// Using a `thread_local!` here relies on the fact that wasm32 can only be single threaded. Previously, we used a
|
|
/// `NonSendMut` parameter, which told Bevy that the system was `!Send`, but now with the removal of `!Send`
|
|
/// resource/system parameter usage, there is no internal guarantee that the system will run in only one thread, so
|
|
/// we need to rely on the platform to make such a guarantee.
|
|
static GILRS: RefCell<Option<gilrs::Gilrs>> = const { RefCell::new(None) };
|
|
}
|
|
|
|
#[derive(Resource)]
|
|
pub(crate) struct Gilrs {
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
cell: SyncCell<gilrs::Gilrs>,
|
|
}
|
|
impl Gilrs {
|
|
#[inline]
|
|
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {
|
|
#[cfg(target_arch = "wasm32")]
|
|
GILRS.with(|g| f(g.borrow_mut().as_mut().expect("GILRS was not initialized")));
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
f(self.cell.get());
|
|
}
|
|
}
|
|
|
|
/// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`].
|
|
#[derive(Debug, Default, Resource)]
|
|
pub(crate) struct GilrsGamepads {
|
|
/// Mapping of [`Entity`] to [`gilrs::GamepadId`].
|
|
pub(crate) entity_to_id: EntityHashMap<gilrs::GamepadId>,
|
|
/// Mapping of [`gilrs::GamepadId`] to [`Entity`].
|
|
pub(crate) id_to_entity: HashMap<gilrs::GamepadId, Entity>,
|
|
}
|
|
|
|
impl GilrsGamepads {
|
|
/// Returns the [`Entity`] assigned to a connected [`gilrs::GamepadId`].
|
|
pub fn get_entity(&self, gamepad_id: gilrs::GamepadId) -> Option<Entity> {
|
|
self.id_to_entity.get(&gamepad_id).copied()
|
|
}
|
|
|
|
/// Returns the [`gilrs::GamepadId`] assigned to a gamepad [`Entity`].
|
|
pub fn get_gamepad_id(&self, entity: Entity) -> Option<gilrs::GamepadId> {
|
|
self.entity_to_id.get(&entity).copied()
|
|
}
|
|
}
|
|
|
|
/// Plugin that provides gamepad handling to an [`App`].
|
|
#[derive(Default)]
|
|
pub struct GilrsPlugin;
|
|
|
|
/// Updates the running gamepad rumble effects.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
|
|
pub struct RumbleSystem;
|
|
|
|
impl Plugin for GilrsPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
match GilrsBuilder::new()
|
|
.with_default_filters(false)
|
|
.set_update_state(false)
|
|
.build()
|
|
{
|
|
Ok(gilrs) => {
|
|
let g = Gilrs {
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
cell: SyncCell::new(gilrs),
|
|
};
|
|
#[cfg(target_arch = "wasm32")]
|
|
GILRS.with(|g| {
|
|
g.replace(Some(gilrs));
|
|
});
|
|
app.insert_resource(g);
|
|
app.init_resource::<GilrsGamepads>();
|
|
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));
|
|
}
|
|
Err(err) => error!("Failed to start Gilrs. {}", err),
|
|
}
|
|
}
|
|
}
|