Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

41
vendor/bevy_gilrs/src/converter.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use bevy_input::gamepad::{GamepadAxis, GamepadButton};
pub fn convert_button(button: gilrs::Button) -> Option<GamepadButton> {
match button {
gilrs::Button::South => Some(GamepadButton::South),
gilrs::Button::East => Some(GamepadButton::East),
gilrs::Button::North => Some(GamepadButton::North),
gilrs::Button::West => Some(GamepadButton::West),
gilrs::Button::C => Some(GamepadButton::C),
gilrs::Button::Z => Some(GamepadButton::Z),
gilrs::Button::LeftTrigger => Some(GamepadButton::LeftTrigger),
gilrs::Button::LeftTrigger2 => Some(GamepadButton::LeftTrigger2),
gilrs::Button::RightTrigger => Some(GamepadButton::RightTrigger),
gilrs::Button::RightTrigger2 => Some(GamepadButton::RightTrigger2),
gilrs::Button::Select => Some(GamepadButton::Select),
gilrs::Button::Start => Some(GamepadButton::Start),
gilrs::Button::Mode => Some(GamepadButton::Mode),
gilrs::Button::LeftThumb => Some(GamepadButton::LeftThumb),
gilrs::Button::RightThumb => Some(GamepadButton::RightThumb),
gilrs::Button::DPadUp => Some(GamepadButton::DPadUp),
gilrs::Button::DPadDown => Some(GamepadButton::DPadDown),
gilrs::Button::DPadLeft => Some(GamepadButton::DPadLeft),
gilrs::Button::DPadRight => Some(GamepadButton::DPadRight),
gilrs::Button::Unknown => None,
}
}
pub fn convert_axis(axis: gilrs::Axis) -> Option<GamepadAxis> {
match axis {
gilrs::Axis::LeftStickX => Some(GamepadAxis::LeftStickX),
gilrs::Axis::LeftStickY => Some(GamepadAxis::LeftStickY),
gilrs::Axis::LeftZ => Some(GamepadAxis::LeftZ),
gilrs::Axis::RightStickX => Some(GamepadAxis::RightStickX),
gilrs::Axis::RightStickY => Some(GamepadAxis::RightStickY),
gilrs::Axis::RightZ => Some(GamepadAxis::RightZ),
// The `axis_dpad_to_button` gilrs filter should filter out all DPadX and DPadY events. If
// it doesn't then we probably need an entry added to the following repo and an update to
// GilRs to use the updated database: https://github.com/gabomdq/SDL_GameControllerDB
gilrs::Axis::Unknown | gilrs::Axis::DPadX | gilrs::Axis::DPadY => None,
}
}

115
vendor/bevy_gilrs/src/gilrs_system.rs vendored Normal file
View File

@@ -0,0 +1,115 @@
use crate::{
converter::{convert_axis, convert_button},
Gilrs, GilrsGamepads,
};
use bevy_ecs::event::EventWriter;
use bevy_ecs::prelude::Commands;
use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
pub fn gilrs_event_startup_system(
mut commands: Commands,
mut gilrs: ResMut<Gilrs>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<GamepadConnectionEvent>,
) {
gilrs.with(|gilrs| {
for (id, gamepad) in gilrs.gamepads() {
// Create entity and add to mapping
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id);
events.write(GamepadConnectionEvent {
gamepad: entity,
connection: GamepadConnection::Connected {
name: gamepad.name().to_string(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
},
});
}
});
}
pub fn gilrs_event_system(
mut commands: Commands,
mut gilrs: ResMut<Gilrs>,
mut gamepads: ResMut<GilrsGamepads>,
mut events: EventWriter<RawGamepadEvent>,
mut connection_events: EventWriter<GamepadConnectionEvent>,
mut button_events: EventWriter<RawGamepadButtonChangedEvent>,
mut axis_event: EventWriter<RawGamepadAxisChangedEvent>,
) {
gilrs.with(|gilrs| {
while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) {
gilrs.update(&gilrs_event);
match gilrs_event.event {
EventType::Connected => {
let pad = gilrs.gamepad(gilrs_event.id);
let entity = gamepads.get_entity(gilrs_event.id).unwrap_or_else(|| {
let entity = commands.spawn_empty().id();
gamepads.id_to_entity.insert(gilrs_event.id, entity);
gamepads.entity_to_id.insert(entity, gilrs_event.id);
entity
});
let event = GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected {
name: pad.name().to_string(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
},
);
events.write(event.clone().into());
connection_events.write(event);
}
EventType::Disconnected => {
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
let event =
GamepadConnectionEvent::new(gamepad, GamepadConnection::Disconnected);
events.write(event.clone().into());
connection_events.write(event);
}
EventType::ButtonChanged(gilrs_button, raw_value, _) => {
let Some(button) = convert_button(gilrs_button) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.write(
RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into(),
);
button_events.write(RawGamepadButtonChangedEvent::new(
gamepad, button, raw_value,
));
}
EventType::AxisChanged(gilrs_axis, raw_value, _) => {
let Some(axis) = convert_axis(gilrs_axis) else {
continue;
};
let gamepad = gamepads
.id_to_entity
.get(&gilrs_event.id)
.copied()
.expect("mapping should exist from connection");
events.write(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into());
axis_event.write(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value));
}
_ => (),
};
}
gilrs.inc();
});
}

115
vendor/bevy_gilrs/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,115 @@
#![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::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
use bevy_platform::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),
}
}
}

185
vendor/bevy_gilrs/src/rumble.rs vendored Normal file
View File

@@ -0,0 +1,185 @@
//! Handle user specified rumble request events.
use crate::{Gilrs, GilrsGamepads};
use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource};
use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest};
use bevy_platform::collections::HashMap;
use bevy_time::{Real, Time};
use bevy_utils::synccell::SyncCell;
use core::time::Duration;
use gilrs::{
ff::{self, BaseEffect, BaseEffectType, Repeat, Replay},
GamepadId,
};
use thiserror::Error;
use tracing::{debug, warn};
/// A rumble effect that is currently in effect.
struct RunningRumble {
/// Duration from app startup when this effect will be finished
deadline: Duration,
/// A ref-counted handle to the specific force-feedback effect
///
/// Dropping it will cause the effect to stop
#[expect(
dead_code,
reason = "We don't need to read this field, as its purpose is to keep the rumble effect going until the field is dropped."
)]
effect: SyncCell<ff::Effect>,
}
#[derive(Error, Debug)]
enum RumbleError {
#[error("gamepad not found")]
GamepadNotFound,
#[error("gilrs error while rumbling gamepad: {0}")]
GilrsError(#[from] ff::Error),
}
/// Contains the gilrs rumble effects that are currently running for each gamepad
#[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`]
rumbles: HashMap<GamepadId, Vec<RunningRumble>>,
}
/// gilrs uses magnitudes from 0 to [`u16::MAX`], while ours go from `0.0` to `1.0` ([`f32`])
fn to_gilrs_magnitude(ratio: f32) -> u16 {
(ratio * u16::MAX as f32) as u16
}
fn get_base_effects(
GamepadRumbleIntensity {
weak_motor,
strong_motor,
}: GamepadRumbleIntensity,
duration: Duration,
) -> Vec<BaseEffect> {
let mut effects = Vec::new();
if strong_motor > 0. {
effects.push(BaseEffect {
kind: BaseEffectType::Strong {
magnitude: to_gilrs_magnitude(strong_motor),
},
scheduling: Replay {
play_for: duration.into(),
..Default::default()
},
..Default::default()
});
}
if weak_motor > 0. {
effects.push(BaseEffect {
kind: BaseEffectType::Strong {
magnitude: to_gilrs_magnitude(weak_motor),
},
..Default::default()
});
}
effects
}
fn handle_rumble_request(
running_rumbles: &mut RunningRumbleEffects,
gilrs: &mut gilrs::Gilrs,
gamepads: &GilrsGamepads,
rumble: GamepadRumbleRequest,
current_time: Duration,
) -> Result<(), RumbleError> {
let gamepad = rumble.gamepad();
let (gamepad_id, _) = gilrs
.gamepads()
.find(|(pad_id, _)| *pad_id == gamepads.get_gamepad_id(gamepad).unwrap())
.ok_or(RumbleError::GamepadNotFound)?;
match rumble {
GamepadRumbleRequest::Stop { .. } => {
// `ff::Effect` uses RAII, dropping = deactivating
running_rumbles.rumbles.remove(&gamepad_id);
}
GamepadRumbleRequest::Add {
duration,
intensity,
..
} => {
let mut effect_builder = ff::EffectBuilder::new();
for effect in get_base_effects(intensity, duration) {
effect_builder.add_effect(effect);
effect_builder.repeat(Repeat::For(duration.into()));
}
let effect = effect_builder.gamepads(&[gamepad_id]).finish(gilrs)?;
effect.play()?;
let gamepad_rumbles = running_rumbles.rumbles.entry(gamepad_id).or_default();
let deadline = current_time + duration;
gamepad_rumbles.push(RunningRumble {
deadline,
effect: SyncCell::new(effect),
});
}
}
Ok(())
}
pub(crate) fn play_gilrs_rumble(
time: Res<Time<Real>>,
mut gilrs: ResMut<Gilrs>,
gamepads: Res<GilrsGamepads>,
mut requests: EventReader<GamepadRumbleRequest>,
mut running_rumbles: ResMut<RunningRumbleEffects>,
) {
gilrs.with(|gilrs| {
let current_time = time.elapsed();
// Remove outdated rumble effects.
for rumbles in running_rumbles.rumbles.values_mut() {
// `ff::Effect` uses RAII, dropping = deactivating
rumbles.retain(|RunningRumble { deadline, .. }| *deadline >= current_time);
}
running_rumbles
.rumbles
.retain(|_gamepad, rumbles| !rumbles.is_empty());
// Add new effects.
for rumble in requests.read().cloned() {
let gamepad = rumble.gamepad();
match handle_rumble_request(&mut running_rumbles, gilrs, &gamepads, rumble, current_time) {
Ok(()) => {}
Err(RumbleError::GilrsError(err)) => {
if let ff::Error::FfNotSupported(_) = err {
debug!("Tried to rumble {gamepad:?}, but it doesn't support force feedback");
} else {
warn!(
"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!");
}
};
}
});
}
#[cfg(test)]
mod tests {
use super::to_gilrs_magnitude;
#[test]
fn magnitude_conversion() {
assert_eq!(to_gilrs_magnitude(1.0), u16::MAX);
assert_eq!(to_gilrs_magnitude(0.0), 0);
// bevy magnitudes of 2.0 don't really make sense, but just make sure
// they convert to something sensible in gilrs anyway.
assert_eq!(to_gilrs_magnitude(2.0), u16::MAX);
// negative bevy magnitudes don't really make sense, but just make sure
// they convert to something sensible in gilrs anyway.
assert_eq!(to_gilrs_magnitude(-1.0), 0);
assert_eq!(to_gilrs_magnitude(-0.1), 0);
}
}