From 03194555c51b1cfac79ed13b510020cd4a483bb0 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 1 Feb 2025 12:01:45 -0800 Subject: [PATCH 01/23] Play around with using `thread_local!` --- crates/bevy_ecs/src/component.rs | 68 ++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 190910d7d0b96..e80f046e2a2a1 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -778,7 +778,11 @@ impl ComponentInfo { /// are not accessed from the wrong thread. #[inline] pub fn is_send_and_sync(&self) -> bool { - self.descriptor.is_send_and_sync + match self.descriptor.sendability { + ComponentSendability::SendAndSync => true, + ComponentSendability::NonSend => false, + ComponentSendability::ThreadLocal => false, + } } /// Create a new [`ComponentInfo`]. @@ -882,6 +886,21 @@ impl SparseSetIndex for ComponentId { } } +/// Indicates how a component is handled when sent between threads. +#[derive(Clone, Debug)] +enum ComponentSendability { + /// The component can safely be sent between threads. + SendAndSync, + + /// The component cannot safely be sent between threads. If an attempt is made to access this + /// component from a different thread, program panics. + NonSend, + + /// The component data will be copied across threads when accessed from a different thread. + /// This uses more memory and mutating across threads is not possible. + ThreadLocal, +} + /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -891,7 +910,7 @@ pub struct ComponentDescriptor { storage_type: StorageType, // SAFETY: This must remain private. It must only be set to "true" if this component is // actually Send + Sync - is_send_and_sync: bool, + sendability: ComponentSendability, type_id: Option, layout: Layout, // SAFETY: this function must be safe to call with pointers pointing to items of the type @@ -907,7 +926,7 @@ impl Debug for ComponentDescriptor { f.debug_struct("ComponentDescriptor") .field("name", &self.name) .field("storage_type", &self.storage_type) - .field("is_send_and_sync", &self.is_send_and_sync) + .field("sendability", &self.sendability) .field("type_id", &self.type_id) .field("layout", &self.layout) .field("mutable", &self.mutable) @@ -931,7 +950,7 @@ impl ComponentDescriptor { Self { name: Cow::Borrowed(core::any::type_name::()), storage_type: T::STORAGE_TYPE, - is_send_and_sync: true, + sendability: ComponentSendability::SendAndSync, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -954,7 +973,7 @@ impl ComponentDescriptor { Self { name: name.into(), storage_type, - is_send_and_sync: true, + sendability: ComponentSendability::SendAndSync, type_id: None, layout, drop, @@ -971,7 +990,7 @@ impl ComponentDescriptor { // PERF: `SparseStorage` may actually be a more // reasonable choice as `storage_type` for resources. storage_type: StorageType::Table, - is_send_and_sync: true, + sendability: ComponentSendability::SendAndSync, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -983,7 +1002,19 @@ impl ComponentDescriptor { Self { name: Cow::Borrowed(core::any::type_name::()), storage_type, - is_send_and_sync: false, + sendability: ComponentSendability::NonSend, + type_id: Some(TypeId::of::()), + layout: Layout::new::(), + drop: needs_drop::().then_some(Self::drop_ptr:: as _), + mutable: true, + } + } + + fn new_thread_local(storage_type: StorageType) -> Self { + Self { + name: Cow::Borrowed(core::any::type_name::()), + storage_type, + sendability: ComponentSendability::ThreadLocal, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -1682,6 +1713,19 @@ impl Components { } } + /// Registers a [thread-local resource](thread_local!) of type `T` with this instance. + /// If a resource of this type has already been registered, this will return + /// the ID of the pre-existing resource. + #[inline] + pub fn register_thread_local(&mut self) -> ComponentId { + // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] + unsafe { + self.get_or_register_resource_with(TypeId::of::(), || { + ComponentDescriptor::new_thread_local::(StorageType::default()) + }) + } + } + /// # Safety /// /// The [`ComponentDescriptor`] must match the [`TypeId`] @@ -1704,6 +1748,16 @@ impl Components { descriptor: ComponentDescriptor, ) -> ComponentId { let component_id = ComponentId(components.len()); + let component = match descriptor.sendability { + ComponentSendability::ThreadLocal => { + #![feature(thread_local)] + #[thread_local] + static comp = ComponentInfo::new(component_id, descriptor); + } + _ => { + ComponentInfo::new(component_id, descriptor) + } + }; components.push(ComponentInfo::new(component_id, descriptor)); component_id } From 3422c4980cd74c39567702a2ffbb559a3026865f Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Wed, 5 Feb 2025 08:20:04 -0800 Subject: [PATCH 02/23] Revert "Play around with using `thread_local!`" This reverts commit 03194555c51b1cfac79ed13b510020cd4a483bb0. --- crates/bevy_ecs/src/component.rs | 68 ++++---------------------------- 1 file changed, 7 insertions(+), 61 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index e80f046e2a2a1..190910d7d0b96 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -778,11 +778,7 @@ impl ComponentInfo { /// are not accessed from the wrong thread. #[inline] pub fn is_send_and_sync(&self) -> bool { - match self.descriptor.sendability { - ComponentSendability::SendAndSync => true, - ComponentSendability::NonSend => false, - ComponentSendability::ThreadLocal => false, - } + self.descriptor.is_send_and_sync } /// Create a new [`ComponentInfo`]. @@ -886,21 +882,6 @@ impl SparseSetIndex for ComponentId { } } -/// Indicates how a component is handled when sent between threads. -#[derive(Clone, Debug)] -enum ComponentSendability { - /// The component can safely be sent between threads. - SendAndSync, - - /// The component cannot safely be sent between threads. If an attempt is made to access this - /// component from a different thread, program panics. - NonSend, - - /// The component data will be copied across threads when accessed from a different thread. - /// This uses more memory and mutating across threads is not possible. - ThreadLocal, -} - /// A value describing a component or resource, which may or may not correspond to a Rust type. #[derive(Clone)] pub struct ComponentDescriptor { @@ -910,7 +891,7 @@ pub struct ComponentDescriptor { storage_type: StorageType, // SAFETY: This must remain private. It must only be set to "true" if this component is // actually Send + Sync - sendability: ComponentSendability, + is_send_and_sync: bool, type_id: Option, layout: Layout, // SAFETY: this function must be safe to call with pointers pointing to items of the type @@ -926,7 +907,7 @@ impl Debug for ComponentDescriptor { f.debug_struct("ComponentDescriptor") .field("name", &self.name) .field("storage_type", &self.storage_type) - .field("sendability", &self.sendability) + .field("is_send_and_sync", &self.is_send_and_sync) .field("type_id", &self.type_id) .field("layout", &self.layout) .field("mutable", &self.mutable) @@ -950,7 +931,7 @@ impl ComponentDescriptor { Self { name: Cow::Borrowed(core::any::type_name::()), storage_type: T::STORAGE_TYPE, - sendability: ComponentSendability::SendAndSync, + is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -973,7 +954,7 @@ impl ComponentDescriptor { Self { name: name.into(), storage_type, - sendability: ComponentSendability::SendAndSync, + is_send_and_sync: true, type_id: None, layout, drop, @@ -990,7 +971,7 @@ impl ComponentDescriptor { // PERF: `SparseStorage` may actually be a more // reasonable choice as `storage_type` for resources. storage_type: StorageType::Table, - sendability: ComponentSendability::SendAndSync, + is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -1002,19 +983,7 @@ impl ComponentDescriptor { Self { name: Cow::Borrowed(core::any::type_name::()), storage_type, - sendability: ComponentSendability::NonSend, - type_id: Some(TypeId::of::()), - layout: Layout::new::(), - drop: needs_drop::().then_some(Self::drop_ptr:: as _), - mutable: true, - } - } - - fn new_thread_local(storage_type: StorageType) -> Self { - Self { - name: Cow::Borrowed(core::any::type_name::()), - storage_type, - sendability: ComponentSendability::ThreadLocal, + is_send_and_sync: false, type_id: Some(TypeId::of::()), layout: Layout::new::(), drop: needs_drop::().then_some(Self::drop_ptr:: as _), @@ -1713,19 +1682,6 @@ impl Components { } } - /// Registers a [thread-local resource](thread_local!) of type `T` with this instance. - /// If a resource of this type has already been registered, this will return - /// the ID of the pre-existing resource. - #[inline] - pub fn register_thread_local(&mut self) -> ComponentId { - // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] - unsafe { - self.get_or_register_resource_with(TypeId::of::(), || { - ComponentDescriptor::new_thread_local::(StorageType::default()) - }) - } - } - /// # Safety /// /// The [`ComponentDescriptor`] must match the [`TypeId`] @@ -1748,16 +1704,6 @@ impl Components { descriptor: ComponentDescriptor, ) -> ComponentId { let component_id = ComponentId(components.len()); - let component = match descriptor.sendability { - ComponentSendability::ThreadLocal => { - #![feature(thread_local)] - #[thread_local] - static comp = ComponentInfo::new(component_id, descriptor); - } - _ => { - ComponentInfo::new(component_id, descriptor) - } - }; components.push(ComponentInfo::new(component_id, descriptor)); component_id } From 22c00cc18e3459704d23de5e8776fca42cfe71a7 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Thu, 6 Feb 2025 15:07:29 -0800 Subject: [PATCH 03/23] Begin moving gilrs from NonSend resource to `thread_local!` --- crates/bevy_gilrs/src/gilrs_system.rs | 166 +++++++++++++------------- crates/bevy_gilrs/src/lib.rs | 19 +-- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 05f9aa02e9692..2922938dbccda 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -2,39 +2,40 @@ use crate::{ converter::{convert_axis, convert_button}, Gilrs, GilrsGamepads, }; -use bevy_ecs::event::EventWriter; +use bevy_ecs::{event::EventWriter, system::Res}; use bevy_ecs::prelude::Commands; -#[cfg(target_arch = "wasm32")] -use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; -use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; +use gilrs::{ev::filter::axis_dpad_to_button, EventType}; pub fn gilrs_event_startup_system( mut commands: Commands, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, + gilrs: Res<'static, Gilrs>, mut gamepads: ResMut, mut events: EventWriter, ) { - for (id, gamepad) in gilrs.0.get().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); + gilrs.into_inner().0.with(|g_lock| { + if let Some(g) = g_lock.get() { + for (id, gamepad) in g.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.send(GamepadConnectionEvent { - gamepad: entity, - connection: GamepadConnection::Connected { - name: gamepad.name().to_string(), - vendor_id: gamepad.vendor_id(), - product_id: gamepad.product_id(), - }, - }); - } + events.send(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( @@ -47,69 +48,72 @@ pub fn gilrs_event_system( mut button_events: EventWriter, mut axis_event: EventWriter, ) { - let gilrs = gilrs.0.get(); - 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 - }); + gilrs.into_inner().0.with(|g_lock| { + if let Some(g) = g_lock.get() { + while let Some(gilrs_event) = g.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(), - }, - ); + let event = GamepadConnectionEvent::new( + entity, + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); - events.send(event.clone().into()); - connection_events.send(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.send(event.clone().into()); - connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(RawGamepadButtonChangedEvent::new( - gamepad, button, raw_value, - )); - } - EventType::AxisChanged(gilrs_axis, raw_value, _) => { - let Some(axis) = convert_axis(gilrs_axis) else { - continue; + events.send(event.clone().into()); + connection_events.send(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.send(event.clone().into()); + connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); + } + _ => (), }; - let gamepad = gamepads - .id_to_entity - .get(&gilrs_event.id) - .copied() - .expect("mapping should exist from connection"); - events.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); } - _ => (), - }; - } - gilrs.inc(); + gilrs.inc(); + } + }); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index b9f1443e5b245..46b74c74a5191 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,19 +14,24 @@ mod converter; mod gilrs_system; mod rumble; +use std::{sync::OnceLock, thread::LocalKey}; + 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 bevy_utils::synccell::SyncCell; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))] -pub(crate) struct Gilrs(pub SyncCell); +thread_local! { + static GILRS: OnceLock = OnceLock::new(); +} + +#[derive(Resource)] +pub(crate) struct Gilrs(pub LocalKey>); /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. #[derive(Debug, Default, Resource)] @@ -65,10 +70,10 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - #[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))); + GILRS.with(move |g| { + g.get_or_init(|| gilrs); + }); + app.insert_resource(Gilrs(GILRS)); app.init_resource::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) From 75326faefcfba1574a70317e100b21f75ddc1432 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Thu, 6 Feb 2025 16:12:37 -0800 Subject: [PATCH 04/23] Use `RefCell` instead of `OnceLock` --- crates/bevy_gilrs/src/gilrs_system.rs | 17 +++++++++-------- crates/bevy_gilrs/src/lib.rs | 8 ++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 2922938dbccda..8549dc637e066 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,3 +1,5 @@ +use std::{borrow::BorrowMut, ops::Deref}; + use crate::{ converter::{convert_axis, convert_button}, Gilrs, GilrsGamepads, @@ -9,7 +11,7 @@ use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent, }; -use gilrs::{ev::filter::axis_dpad_to_button, EventType}; +use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( mut commands: Commands, @@ -17,8 +19,8 @@ pub fn gilrs_event_startup_system( mut gamepads: ResMut, mut events: EventWriter, ) { - gilrs.into_inner().0.with(|g_lock| { - if let Some(g) = g_lock.get() { + gilrs.into_inner().0.with(|g_ref| { + if let Some(g) = g_ref.borrow().deref() { for (id, gamepad) in g.gamepads() { // Create entity and add to mapping let entity = commands.spawn_empty().id(); @@ -40,17 +42,16 @@ pub fn gilrs_event_startup_system( pub fn gilrs_event_system( mut commands: Commands, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, + gilrs: Res<'static, Gilrs>, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - gilrs.into_inner().0.with(|g_lock| { - if let Some(g) = g_lock.get() { - while let Some(gilrs_event) = g.next_event().filter_ev(&axis_dpad_to_button, gilrs) { + gilrs.into_inner().0.with(|g_ref| { + if let Some(gilrs) = g_ref.borrow_mut().as_mut() { + 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 => { diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 46b74c74a5191..3cf557f81d099 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,7 +14,7 @@ mod converter; mod gilrs_system; mod rumble; -use std::{sync::OnceLock, thread::LocalKey}; +use std::{cell::RefCell, thread::LocalKey}; use bevy_app::{App, Plugin, PostUpdate, PreStartup, PreUpdate}; use bevy_ecs::entity::hash_map::EntityHashMap; @@ -27,11 +27,11 @@ use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; thread_local! { - static GILRS: OnceLock = OnceLock::new(); + static GILRS: RefCell> = RefCell::new(None); } #[derive(Resource)] -pub(crate) struct Gilrs(pub LocalKey>); +pub(crate) struct Gilrs(pub LocalKey>>); /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. #[derive(Debug, Default, Resource)] @@ -71,7 +71,7 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { GILRS.with(move |g| { - g.get_or_init(|| gilrs); + g.replace(Some(gilrs)); }); app.insert_resource(Gilrs(GILRS)); app.init_resource::(); From fb464a72a9c925e8588b6f0ed3070584aef05ce0 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Fri, 7 Feb 2025 08:50:52 -0800 Subject: [PATCH 05/23] Use `thread_local!` in rumble.rs --- crates/bevy_gilrs/src/rumble.rs | 64 ++++++++++++++++----------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 53a0c945cedef..4d6d43972fb26 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,8 +1,6 @@ //! Handle user specified rumble request events. use crate::{Gilrs, GilrsGamepads}; 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_platform_support::collections::HashMap; use bevy_time::{Real, Time}; @@ -128,42 +126,44 @@ fn handle_rumble_request( } pub(crate) fn play_gilrs_rumble( time: Res>, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, + gilrs: ResMut<'static, Gilrs>, gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - let gilrs = gilrs.0.get(); - 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}" - ); - } + gilrs.into_inner().0.with(|g_ref| { + if let Some(gilrs) = g_ref.borrow_mut().as_mut() { + 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); } - Err(RumbleError::GamepadNotFound) => { - warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); + 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)] From 38ffe2c99388c5e721fdbd9b8126b15e20a227e0 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Fri, 7 Feb 2025 12:43:49 -0800 Subject: [PATCH 06/23] Try not using 'static --- crates/bevy_gilrs/src/gilrs_system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 8549dc637e066..7a7f49e36b5aa 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,4 +1,4 @@ -use std::{borrow::BorrowMut, ops::Deref}; +use std::ops::Deref; use crate::{ converter::{convert_axis, convert_button}, @@ -15,11 +15,11 @@ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( mut commands: Commands, - gilrs: Res<'static, Gilrs>, + gilrs: Res, mut gamepads: ResMut, mut events: EventWriter, ) { - gilrs.into_inner().0.with(|g_ref| { + gilrs.0.with(|g_ref| { if let Some(g) = g_ref.borrow().deref() { for (id, gamepad) in g.gamepads() { // Create entity and add to mapping From ca9829c1ebca3f8f7cbf173d90e3cd19574f0d51 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Fri, 7 Feb 2025 15:15:36 -0800 Subject: [PATCH 07/23] Revert all --- crates/bevy_gilrs/src/gilrs_system.rs | 169 +++++++++++++------------- crates/bevy_gilrs/src/lib.rs | 19 ++- crates/bevy_gilrs/src/rumble.rs | 64 +++++----- 3 files changed, 121 insertions(+), 131 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 7a7f49e36b5aa..05f9aa02e9692 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,11 +1,11 @@ -use std::ops::Deref; - use crate::{ converter::{convert_axis, convert_button}, Gilrs, GilrsGamepads, }; -use bevy_ecs::{event::EventWriter, system::Res}; +use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::Commands; +#[cfg(target_arch = "wasm32")] +use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, @@ -15,106 +15,101 @@ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( mut commands: Commands, - gilrs: Res, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, ) { - gilrs.0.with(|g_ref| { - if let Some(g) = g_ref.borrow().deref() { - for (id, gamepad) in g.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); + for (id, gamepad) in gilrs.0.get().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.send(GamepadConnectionEvent { - gamepad: entity, - connection: GamepadConnection::Connected { - name: gamepad.name().to_string(), - vendor_id: gamepad.vendor_id(), - product_id: gamepad.product_id(), - }, - }); - } - } - }); + events.send(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, - gilrs: Res<'static, Gilrs>, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - gilrs.into_inner().0.with(|g_ref| { - if let Some(gilrs) = g_ref.borrow_mut().as_mut() { - 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 gilrs = gilrs.0.get(); + 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(), - }, - ); + let event = GamepadConnectionEvent::new( + entity, + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); - events.send(event.clone().into()); - connection_events.send(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.send(event.clone().into()); - connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); - } - _ => (), + events.send(event.clone().into()); + connection_events.send(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.send(event.clone().into()); + connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); } - gilrs.inc(); - } - }); + _ => (), + }; + } + gilrs.inc(); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 3cf557f81d099..b9f1443e5b245 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,24 +14,19 @@ mod converter; mod gilrs_system; mod rumble; -use std::{cell::RefCell, thread::LocalKey}; - 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 bevy_utils::synccell::SyncCell; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -thread_local! { - static GILRS: RefCell> = RefCell::new(None); -} - -#[derive(Resource)] -pub(crate) struct Gilrs(pub LocalKey>>); +#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))] +pub(crate) struct Gilrs(pub SyncCell); /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. #[derive(Debug, Default, Resource)] @@ -70,10 +65,10 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - GILRS.with(move |g| { - g.replace(Some(gilrs)); - }); - app.insert_resource(Gilrs(GILRS)); + #[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::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 4d6d43972fb26..53a0c945cedef 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,6 +1,8 @@ //! Handle user specified rumble request events. use crate::{Gilrs, GilrsGamepads}; 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_platform_support::collections::HashMap; use bevy_time::{Real, Time}; @@ -126,44 +128,42 @@ fn handle_rumble_request( } pub(crate) fn play_gilrs_rumble( time: Res>, - gilrs: ResMut<'static, Gilrs>, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - gilrs.into_inner().0.with(|g_ref| { - if let Some(gilrs) = g_ref.borrow_mut().as_mut() { - 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); + let gilrs = gilrs.0.get(); + 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}" + ); + } } - 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!"); - } - }; + Err(RumbleError::GamepadNotFound) => { + warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); } - } - }); + }; + } } #[cfg(test)] From df8577975b1316c164cf51a443bca481479e749c Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 8 Feb 2025 10:26:30 -0800 Subject: [PATCH 08/23] Use `thread_local!` to replace `!Send` gilrs resource --- crates/bevy_gilrs/src/gilrs_system.rs | 174 +++++++++++++------------- crates/bevy_gilrs/src/lib.rs | 16 +-- crates/bevy_gilrs/src/rumble.rs | 65 +++++----- 3 files changed, 131 insertions(+), 124 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 05f9aa02e9692..531e01b01c7f7 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,11 +1,9 @@ use crate::{ converter::{convert_axis, convert_button}, - Gilrs, GilrsGamepads, + GilrsGamepads, GILRS, }; use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::Commands; -#[cfg(target_arch = "wasm32")] -use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, @@ -15,101 +13,109 @@ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( mut commands: Commands, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, ) { - for (id, gamepad) in gilrs.0.get().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); + GILRS.with(|gilrs_ref_cell| { + for (id, gamepad) in gilrs_ref_cell + .borrow() + .as_ref() + .expect("gilrs should have been initialized") + .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.send(GamepadConnectionEvent { - gamepad: entity, - connection: GamepadConnection::Connected { - name: gamepad.name().to_string(), - vendor_id: gamepad.vendor_id(), - product_id: gamepad.product_id(), - }, - }); - } + events.send(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, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - let gilrs = gilrs.0.get(); - 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 - }); + GILRS.with(|gilrs_ref_cell| { + let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); + let gilrs = gilrs_ref.as_mut().expect(""); + 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(), - }, - ); + let event = GamepadConnectionEvent::new( + entity, + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); - events.send(event.clone().into()); - connection_events.send(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.send(event.clone().into()); - connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); - } - _ => (), - }; - } - gilrs.inc(); + events.send(event.clone().into()); + connection_events.send(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.send(event.clone().into()); + connection_events.send(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 + .send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); + } + _ => (), + }; + } + gilrs.inc(); + }); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index b9f1443e5b245..bf884912d039d 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,19 +14,24 @@ mod converter; mod gilrs_system; mod rumble; +use std::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 bevy_utils::synccell::SyncCell; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))] -pub(crate) struct Gilrs(pub SyncCell); +thread_local! { + /// Stores main object responsible of managing gamepads. + /// + /// Inner `Option` defaults to `None` and becomes `Some` upon initialization in `GilrsPlugin::build()`. + pub static GILRS: RefCell> = RefCell::new(None); +} /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. #[derive(Debug, Default, Resource)] @@ -65,10 +70,7 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - #[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))); + GILRS.set(Some(gilrs)); app.init_resource::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 53a0c945cedef..037cf0af3ca2a 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,8 +1,6 @@ //! Handle user specified rumble request events. -use crate::{Gilrs, GilrsGamepads}; +use crate::{GilrsGamepads, 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_platform_support::collections::HashMap; use bevy_time::{Real, Time}; @@ -128,42 +126,43 @@ fn handle_rumble_request( } pub(crate) fn play_gilrs_rumble( time: Res>, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - let gilrs = gilrs.0.get(); - 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}" + GILRS.with(|gilrs_ref_cell| { + let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); + let gilrs = gilrs_ref.as_mut().expect(""); + 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!"); - } - }; - } + Err(RumbleError::GamepadNotFound) => { + warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); + } + }; + } + }); } #[cfg(test)] From 2ba4687702e296c4178abd01374b75705686118b Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Mon, 10 Feb 2025 17:11:15 -0800 Subject: [PATCH 09/23] Replace NonSend resources in Winit --- crates/bevy_winit/src/lib.rs | 8 +++++++- crates/bevy_winit/src/state.rs | 8 +++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index c2a3eddc52f04..60889b0a4bcb5 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -19,6 +19,7 @@ use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_window::{RawHandleWrapperHolder, WindowEvent}; use core::marker::PhantomData; +use std::{any::Any, cell::RefCell}; use winit::{event_loop::EventLoop, window::WindowId}; use bevy_a11y::AccessibilityRequested; @@ -53,6 +54,10 @@ mod winit_config; mod winit_monitors; mod winit_windows; +thread_local! { + pub static EVENT_LOOP: RefCell>> = RefCell::new(None); +} + /// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input /// events. /// @@ -144,9 +149,10 @@ impl Plugin for WinitPlugin { .build() .expect("Failed to build event loop"); + EVENT_LOOP.set(Some(Box::new(event_loop))); // `winit`'s windows are bound to the event loop that created them, so the event loop must // be inserted as a resource here to pass it onto the runner. - app.insert_non_send_resource(event_loop); + //app.insert_resource(event_loop); } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 0c1a02e0ce0b4..17776f0e8b144 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -26,6 +26,7 @@ use bevy_platform_support::time::Instant; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; use core::marker::PhantomData; +use std::any::Any; #[cfg(target_arch = "wasm32")] use winit::platform::web::EventLoopExtWebSys; use winit::{ @@ -51,7 +52,7 @@ use crate::{ converters, create_windows, system::{create_monitors, CachedWindow}, AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper, - RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, + RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, EVENT_LOOP, }; /// Persistent state that is used to run the [`App`] according to the current @@ -871,10 +872,7 @@ pub fn winit_runner(mut app: App) -> AppExit { app.cleanup(); } - let event_loop = app - .world_mut() - .remove_non_send_resource::>() - .unwrap(); + let event_loop = EVENT_LOOP.take().expect("event loop was either never initialized or already taken after initialization").downcast::>().expect("event loop passed into Winit needs to be of type `EventLoop`"); app.world_mut() .insert_resource(EventLoopProxyWrapper(event_loop.create_proxy())); From 4ec1dab9305fe34cf917a6f4df1360f4225ebbaf Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Wed, 12 Feb 2025 18:43:01 -0800 Subject: [PATCH 10/23] Add missing panic messages --- crates/bevy_gilrs/src/gilrs_system.rs | 2 +- crates/bevy_gilrs/src/rumble.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 531e01b01c7f7..a60cf830a2f5e 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -50,7 +50,7 @@ pub fn gilrs_event_system( ) { GILRS.with(|gilrs_ref_cell| { let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); - let gilrs = gilrs_ref.as_mut().expect(""); + let gilrs = gilrs_ref.as_mut().expect("gilrs should have been initialized"); while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) { gilrs.update(&gilrs_event); match gilrs_event.event { diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 037cf0af3ca2a..ac2470c7e4d0f 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -132,7 +132,7 @@ pub(crate) fn play_gilrs_rumble( ) { GILRS.with(|gilrs_ref_cell| { let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); - let gilrs = gilrs_ref.as_mut().expect(""); + let gilrs = gilrs_ref.as_mut().expect("gilrs should have been initialized"); let current_time = time.elapsed(); // Remove outdated rumble effects. for rumbles in running_rumbles.rumbles.values_mut() { From 662aec43acca58776bac549adfcd188444cdae52 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 07:22:53 -0800 Subject: [PATCH 11/23] Revert GILRS work --- crates/bevy_gilrs/src/gilrs_system.rs | 174 +++++++++++++------------- crates/bevy_gilrs/src/lib.rs | 16 ++- crates/bevy_gilrs/src/rumble.rs | 65 +++++----- 3 files changed, 124 insertions(+), 131 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index a60cf830a2f5e..05f9aa02e9692 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,9 +1,11 @@ use crate::{ converter::{convert_axis, convert_button}, - GilrsGamepads, GILRS, + Gilrs, GilrsGamepads, }; use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::Commands; +#[cfg(target_arch = "wasm32")] +use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, @@ -13,109 +15,101 @@ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( mut commands: Commands, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, ) { - GILRS.with(|gilrs_ref_cell| { - for (id, gamepad) in gilrs_ref_cell - .borrow() - .as_ref() - .expect("gilrs should have been initialized") - .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); + for (id, gamepad) in gilrs.0.get().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.send(GamepadConnectionEvent { - gamepad: entity, - connection: GamepadConnection::Connected { - name: gamepad.name().to_string(), - vendor_id: gamepad.vendor_id(), - product_id: gamepad.product_id(), - }, - }); - } - }); + events.send(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, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - GILRS.with(|gilrs_ref_cell| { - let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); - let gilrs = gilrs_ref.as_mut().expect("gilrs should have been initialized"); - 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 gilrs = gilrs.0.get(); + 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(), - }, - ); + let event = GamepadConnectionEvent::new( + entity, + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); - events.send(event.clone().into()); - connection_events.send(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.send(event.clone().into()); - connection_events.send(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 - .send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); - } - _ => (), - }; - } - gilrs.inc(); - }); + events.send(event.clone().into()); + connection_events.send(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.send(event.clone().into()); + connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); + } + _ => (), + }; + } + gilrs.inc(); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index bf884912d039d..b9f1443e5b245 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,24 +14,19 @@ mod converter; mod gilrs_system; mod rumble; -use std::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 bevy_utils::synccell::SyncCell; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -thread_local! { - /// Stores main object responsible of managing gamepads. - /// - /// Inner `Option` defaults to `None` and becomes `Some` upon initialization in `GilrsPlugin::build()`. - pub static GILRS: RefCell> = RefCell::new(None); -} +#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))] +pub(crate) struct Gilrs(pub SyncCell); /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. #[derive(Debug, Default, Resource)] @@ -70,7 +65,10 @@ impl Plugin for GilrsPlugin { .build() { Ok(gilrs) => { - GILRS.set(Some(gilrs)); + #[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::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index ac2470c7e4d0f..53a0c945cedef 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,6 +1,8 @@ //! Handle user specified rumble request events. -use crate::{GilrsGamepads, GILRS}; +use crate::{Gilrs, GilrsGamepads}; 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_platform_support::collections::HashMap; use bevy_time::{Real, Time}; @@ -126,43 +128,42 @@ fn handle_rumble_request( } pub(crate) fn play_gilrs_rumble( time: Res>, + #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - GILRS.with(|gilrs_ref_cell| { - let mut gilrs_ref = gilrs_ref_cell.borrow_mut(); - let gilrs = gilrs_ref.as_mut().expect("gilrs should have been initialized"); - 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}" + let gilrs = gilrs.0.get(); + 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!"); } - }; - } - }); + } + Err(RumbleError::GamepadNotFound) => { + warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); + } + }; + } } #[cfg(test)] From c8e428bf8e9564bf008df28bc81fb34842114f78 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 16:05:45 -0800 Subject: [PATCH 12/23] Replace NonSend resources with `thread_local!` in bevy_gilrs --- crates/bevy_gilrs/src/gilrs_system.rs | 76 ++++++++++++++++++++++++--- crates/bevy_gilrs/src/lib.rs | 15 +++++- crates/bevy_gilrs/src/rumble.rs | 38 ++++++++++++-- 3 files changed, 115 insertions(+), 14 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 05f9aa02e9692..8c923d442dab8 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -2,10 +2,12 @@ use crate::{ converter::{convert_axis, convert_button}, Gilrs, GilrsGamepads, }; + +#[cfg(target_arch = "wasm32")] +use crate::GILRS; + use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::Commands; -#[cfg(target_arch = "wasm32")] -use bevy_ecs::system::NonSendMut; use bevy_ecs::system::ResMut; use bevy_input::gamepad::{ GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent, @@ -14,13 +16,38 @@ use bevy_input::gamepad::{ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( - mut commands: Commands, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + commands: Commands, #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, + gamepads: ResMut, + events: EventWriter, +) { + #[cfg(target_arch = "wasm32")] + GILRS.with(|g| { + let g_ref = g.borrow(); + let gilrs = g_ref.as_ref().expect("GILRS was not initialized"); + gilrs_event_startup_system_inner( + commands, + gilrs, + gamepads, + events + ); + }); + #[cfg(not(target_arch = "wasm32"))] + gilrs_event_startup_system_inner( + commands, + gilrs.0.get(), + gamepads, + events + ); +} + +fn gilrs_event_startup_system_inner( + mut commands: Commands, + gilrs: &gilrs::Gilrs, mut gamepads: ResMut, mut events: EventWriter, ) { - for (id, gamepad) in gilrs.0.get().gamepads() { + 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); @@ -38,16 +65,49 @@ pub fn gilrs_event_startup_system( } pub fn gilrs_event_system( - mut commands: Commands, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, + commands: Commands, #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, + gamepads: ResMut, + events: EventWriter, + connection_events: EventWriter, + button_events: EventWriter, + axis_event: EventWriter, +) { + #[cfg(target_arch = "wasm32")] + GILRS.with(|g| { + let g_ref = g.borrow(); + let gilrs = g_ref.as_ref().expect("GILRS was not initialized"); + gilrs_event_system_inner( + commands, + gilrs, + gamepads, + events, + connection_events, + button_events, + axis_event + ); + }); + #[cfg(not(target_arch = "wasm32"))] + gilrs_event_system_inner( + commands, + gilrs.0.get(), + gamepads, + events, + connection_events, + button_events, + axis_event + ); +} + +fn gilrs_event_system_inner( + mut commands: Commands, + gilrs: &mut gilrs::Gilrs, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - let gilrs = gilrs.0.get(); while let Some(gilrs_event) = gilrs.next_event().filter_ev(&axis_dpad_to_button, gilrs) { gilrs.update(&gilrs_event); match gilrs_event.event { diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index b9f1443e5b245..6a178750d3b89 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,6 +14,9 @@ mod converter; mod gilrs_system; mod rumble; +#[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::*; @@ -25,7 +28,13 @@ use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -#[cfg_attr(not(target_arch = "wasm32"), derive(Resource))] +// Temporary replacement for storing gilrs data as a !Send resource. +// Will be replaced with a long-term solution when issue #17667 is completed. +#[cfg(target_arch = "wasm32")] +thread_local!(pub static GILRS: RefCell> = const { RefCell::new(None) }); + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Resource)] pub(crate) struct Gilrs(pub SyncCell); /// A [`resource`](Resource) with the mapping of connected [`gilrs::GamepadId`] and their [`Entity`]. @@ -66,7 +75,9 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { #[cfg(target_arch = "wasm32")] - app.insert_non_send_resource(Gilrs(SyncCell::new(gilrs))); + GILRS.with(|g| { + g.replace(Some(gilrs)); + }); #[cfg(not(target_arch = "wasm32"))] app.insert_resource(Gilrs(SyncCell::new(gilrs))); app.init_resource::(); diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 53a0c945cedef..51fc40138b032 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,8 +1,10 @@ //! Handle user specified rumble request events. use crate::{Gilrs, GilrsGamepads}; -use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; + #[cfg(target_arch = "wasm32")] -use bevy_ecs::system::NonSendMut; +use crate::GILRS; + +use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; use bevy_platform_support::collections::HashMap; use bevy_time::{Real, Time}; @@ -126,15 +128,43 @@ fn handle_rumble_request( Ok(()) } + pub(crate) fn play_gilrs_rumble( time: Res>, - #[cfg(target_arch = "wasm32")] mut gilrs: NonSendMut, #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, gamepads: Res, + requests: EventReader, + running_rumbles: ResMut, +) { + #[cfg(target_arch = "wasm32")] + GILRS.with(|g| { + let mut g_ref = g.borrow_mut(); + let gilrs = g_ref.as_mut().expect("GILRS was not initialized"); + play_gilrs_rumble_inner( + time, + gilrs, + gamepads, + requests, + running_rumbles + ); + }); + #[cfg(not(target_arch = "wasm32"))] + play_gilrs_rumble_inner( + time, + gilrs.0.get(), + gamepads, + requests, + running_rumbles + ); +} + +fn play_gilrs_rumble_inner( + time: Res>, + gilrs: &mut gilrs::Gilrs, + gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - let gilrs = gilrs.0.get(); let current_time = time.elapsed(); // Remove outdated rumble effects. for rumbles in running_rumbles.rumbles.values_mut() { From f35bfda631f61b18eb80ce032e056ffb6c783b44 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:01:01 -0800 Subject: [PATCH 13/23] Format --- crates/bevy_gilrs/src/gilrs_system.rs | 18 ++++-------------- crates/bevy_gilrs/src/rumble.rs | 16 ++-------------- crates/bevy_winit/src/state.rs | 6 +++++- 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 8c923d442dab8..7744751d8e81c 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -25,20 +25,10 @@ pub fn gilrs_event_startup_system( GILRS.with(|g| { let g_ref = g.borrow(); let gilrs = g_ref.as_ref().expect("GILRS was not initialized"); - gilrs_event_startup_system_inner( - commands, - gilrs, - gamepads, - events - ); + gilrs_event_startup_system_inner(commands, gilrs, gamepads, events); }); #[cfg(not(target_arch = "wasm32"))] - gilrs_event_startup_system_inner( - commands, - gilrs.0.get(), - gamepads, - events - ); + gilrs_event_startup_system_inner(commands, gilrs.0.get(), gamepads, events); } fn gilrs_event_startup_system_inner( @@ -84,7 +74,7 @@ pub fn gilrs_event_system( events, connection_events, button_events, - axis_event + axis_event, ); }); #[cfg(not(target_arch = "wasm32"))] @@ -95,7 +85,7 @@ pub fn gilrs_event_system( events, connection_events, button_events, - axis_event + axis_event, ); } diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 51fc40138b032..66644424124c6 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -140,22 +140,10 @@ pub(crate) fn play_gilrs_rumble( GILRS.with(|g| { let mut g_ref = g.borrow_mut(); let gilrs = g_ref.as_mut().expect("GILRS was not initialized"); - play_gilrs_rumble_inner( - time, - gilrs, - gamepads, - requests, - running_rumbles - ); + play_gilrs_rumble_inner(time, gilrs, gamepads, requests, running_rumbles); }); #[cfg(not(target_arch = "wasm32"))] - play_gilrs_rumble_inner( - time, - gilrs.0.get(), - gamepads, - requests, - running_rumbles - ); + play_gilrs_rumble_inner(time, gilrs.0.get(), gamepads, requests, running_rumbles); } fn play_gilrs_rumble_inner( diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 17776f0e8b144..a53210808d6a8 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -872,7 +872,11 @@ pub fn winit_runner(mut app: App) -> AppExit { app.cleanup(); } - let event_loop = EVENT_LOOP.take().expect("event loop was either never initialized or already taken after initialization").downcast::>().expect("event loop passed into Winit needs to be of type `EventLoop`"); + let event_loop = EVENT_LOOP + .take() + .expect("event loop was either never initialized or already taken after initialization") + .downcast::>() + .expect("event loop passed into Winit needs to be of type `EventLoop`"); app.world_mut() .insert_resource(EventLoopProxyWrapper(event_loop.create_proxy())); From 69915155414e9a00eb6a37443d2a1035ad7a34fb Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:08:16 -0800 Subject: [PATCH 14/23] Remove std imports --- crates/bevy_winit/src/lib.rs | 3 +-- crates/bevy_winit/src/state.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 60889b0a4bcb5..df2f693850e0a 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -18,8 +18,7 @@ use bevy_derive::Deref; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_window::{RawHandleWrapperHolder, WindowEvent}; -use core::marker::PhantomData; -use std::{any::Any, cell::RefCell}; +use core::{any::Any, cell::RefCell, marker::PhantomData}; use winit::{event_loop::EventLoop, window::WindowId}; use bevy_a11y::AccessibilityRequested; diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index a53210808d6a8..260b99e9cdf6f 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -26,7 +26,6 @@ use bevy_platform_support::time::Instant; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; use core::marker::PhantomData; -use std::any::Any; #[cfg(target_arch = "wasm32")] use winit::platform::web::EventLoopExtWebSys; use winit::{ From be4bc133ea513df71341dd3120b67f1fd8f914f6 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:08:30 -0800 Subject: [PATCH 15/23] Add docs for `EVENT_LOOP` --- crates/bevy_winit/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index df2f693850e0a..f13582bb90207 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -54,6 +54,8 @@ mod winit_monitors; mod winit_windows; thread_local! { + /// Temporary storage of event loop to replace usage of `!Send` resources. + /// This will be replaced with proper storage of `!Send` data after issue #17667 is complete. pub static EVENT_LOOP: RefCell>> = RefCell::new(None); } From 8627eb3fd087b51118598f8594b18bf41cbd8264 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:44:07 -0800 Subject: [PATCH 16/23] Properly gate non-wasm imports behind target flag --- crates/bevy_gilrs/src/gilrs_system.rs | 5 ++++- crates/bevy_gilrs/src/lib.rs | 4 +++- crates/bevy_gilrs/src/rumble.rs | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 7744751d8e81c..6eaa1bfcc1870 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,8 +1,11 @@ use crate::{ converter::{convert_axis, convert_button}, - Gilrs, GilrsGamepads, + GilrsGamepads, }; +#[cfg(not(target_arch = "wasm32"))] +use crate::Gilrs; + #[cfg(target_arch = "wasm32")] use crate::GILRS; diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 6a178750d3b89..6bb676d4e8c9c 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -14,6 +14,9 @@ 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; @@ -22,7 +25,6 @@ use bevy_ecs::entity::hash_map::EntityHashMap; use bevy_ecs::prelude::*; use bevy_input::InputSystem; use bevy_platform_support::collections::HashMap; -use bevy_utils::synccell::SyncCell; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 66644424124c6..dd79a702223fd 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,5 +1,8 @@ //! Handle user specified rumble request events. -use crate::{Gilrs, GilrsGamepads}; +use crate::GilrsGamepads; + +#[cfg(not(target_arch = "wasm32"))] +use crate::Gilrs; #[cfg(target_arch = "wasm32")] use crate::GILRS; From 2bd208c22b8a8d006eb1ecd6c5af4e5d45543a03 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:44:38 -0800 Subject: [PATCH 17/23] Fix differing mutability --- crates/bevy_gilrs/src/gilrs_system.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 6eaa1bfcc1870..704405751a14d 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -68,8 +68,8 @@ pub fn gilrs_event_system( ) { #[cfg(target_arch = "wasm32")] GILRS.with(|g| { - let g_ref = g.borrow(); - let gilrs = g_ref.as_ref().expect("GILRS was not initialized"); + let mut g_ref = g.borrow_mut(); + let gilrs = g_ref.as_mut().expect("GILRS was not initialized"); gilrs_event_system_inner( commands, gilrs, From 2ad552bfe4b429c5616ad5dd938c63a9716c04bc Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:45:01 -0800 Subject: [PATCH 18/23] Add doc to `GILRS` `thread_local!` --- crates/bevy_gilrs/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 6bb676d4e8c9c..50adda0e0376b 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -30,10 +30,12 @@ use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; use rumble::{play_gilrs_rumble, RunningRumbleEffects}; use tracing::error; -// Temporary replacement for storing gilrs data as a !Send resource. -// Will be replaced with a long-term solution when issue #17667 is completed. #[cfg(target_arch = "wasm32")] -thread_local!(pub static GILRS: RefCell> = const { RefCell::new(None) }); +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. + pub static GILRS: RefCell> = const { RefCell::new(None) }; +} #[cfg(not(target_arch = "wasm32"))] #[derive(Resource)] From 6536e8581e20953a5d5c166499fc884533362a2c Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 15 Feb 2025 18:51:57 -0800 Subject: [PATCH 19/23] Clean up comments and docs --- crates/bevy_winit/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index f13582bb90207..9ecb6301294ce 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -56,6 +56,8 @@ mod winit_windows; thread_local! { /// Temporary storage of event loop to replace usage of `!Send` resources. /// This will be replaced with proper storage of `!Send` data after issue #17667 is complete. + /// + /// Here we use runtime type checking because generics cannot be used with `static`. pub static EVENT_LOOP: RefCell>> = RefCell::new(None); } @@ -150,10 +152,11 @@ impl Plugin for WinitPlugin { .build() .expect("Failed to build event loop"); - EVENT_LOOP.set(Some(Box::new(event_loop))); // `winit`'s windows are bound to the event loop that created them, so the event loop must - // be inserted as a resource here to pass it onto the runner. - //app.insert_resource(event_loop); + // be set as a `thread_local!` here to pass it onto the runner. + // + // This a temporary solution to replace usage of `!Send` resources. See issue #17667 for more info. + EVENT_LOOP.set(Some(Box::new(event_loop))); } } From 266c204c74a953da631460afa641f7a3ebac69b9 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 22 Feb 2025 08:26:35 -0800 Subject: [PATCH 20/23] Pass event loop directly into runner method instead of using `thread_local!` --- crates/bevy_winit/src/lib.rs | 26 ++++++-------------------- crates/bevy_winit/src/state.rs | 10 ++-------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 9ecb6301294ce..7cac158b959e7 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -18,7 +18,7 @@ use bevy_derive::Deref; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_window::{RawHandleWrapperHolder, WindowEvent}; -use core::{any::Any, cell::RefCell, marker::PhantomData}; +use core::marker::PhantomData; use winit::{event_loop::EventLoop, window::WindowId}; use bevy_a11y::AccessibilityRequested; @@ -53,14 +53,6 @@ mod winit_config; mod winit_monitors; mod winit_windows; -thread_local! { - /// Temporary storage of event loop to replace usage of `!Send` resources. - /// This will be replaced with proper storage of `!Send` data after issue #17667 is complete. - /// - /// Here we use runtime type checking because generics cannot be used with `static`. - pub static EVENT_LOOP: RefCell>> = RefCell::new(None); -} - /// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input /// events. /// @@ -128,11 +120,15 @@ impl Plugin for WinitPlugin { event_loop_builder.with_android_app(bevy_window::ANDROID_APP.get().expect(msg).clone()); } + let event_loop = event_loop_builder + .build() + .expect("Failed to build event loop"); + app.init_non_send_resource::() .init_resource::() .init_resource::() .add_event::() - .set_runner(winit_runner::) + .set_runner(|app| winit_runner(app, event_loop)) .add_systems( Last, ( @@ -147,16 +143,6 @@ impl Plugin for WinitPlugin { app.add_plugins(AccessKitPlugin); app.add_plugins(cursor::CursorPlugin); - - let event_loop = event_loop_builder - .build() - .expect("Failed to build event loop"); - - // `winit`'s windows are bound to the event loop that created them, so the event loop must - // be set as a `thread_local!` here to pass it onto the runner. - // - // This a temporary solution to replace usage of `!Send` resources. See issue #17667 for more info. - EVENT_LOOP.set(Some(Box::new(event_loop))); } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 260b99e9cdf6f..ab479f8182f03 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -51,7 +51,7 @@ use crate::{ converters, create_windows, system::{create_monitors, CachedWindow}, AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper, - RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, EVENT_LOOP, + RawWinitWindowEvent, UpdateMode, WinitSettings, WinitWindows, }; /// Persistent state that is used to run the [`App`] according to the current @@ -865,18 +865,12 @@ impl WinitAppRunnerState { /// /// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the /// `EventLoop`. -pub fn winit_runner(mut app: App) -> AppExit { +pub fn winit_runner(mut app: App, event_loop: EventLoop) -> AppExit { if app.plugins_state() == PluginsState::Ready { app.finish(); app.cleanup(); } - let event_loop = EVENT_LOOP - .take() - .expect("event loop was either never initialized or already taken after initialization") - .downcast::>() - .expect("event loop passed into Winit needs to be of type `EventLoop`"); - app.world_mut() .insert_resource(EventLoopProxyWrapper(event_loop.create_proxy())); From 31056682b2cfcce615a658769db650310cff71db Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 22 Feb 2025 09:38:39 -0800 Subject: [PATCH 21/23] Move platform-dependent logic to new `Gilrs::with()` method --- crates/bevy_gilrs/src/gilrs_system.rs | 222 ++++++++++---------------- crates/bevy_gilrs/src/lib.rs | 24 ++- crates/bevy_gilrs/src/rumble.rs | 87 ++++------ 3 files changed, 136 insertions(+), 197 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 704405751a14d..4f9bdbf5ae49b 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -1,14 +1,7 @@ use crate::{ converter::{convert_axis, convert_button}, - GilrsGamepads, + Gilrs, GilrsGamepads, }; - -#[cfg(not(target_arch = "wasm32"))] -use crate::Gilrs; - -#[cfg(target_arch = "wasm32")] -use crate::GILRS; - use bevy_ecs::event::EventWriter; use bevy_ecs::prelude::Commands; use bevy_ecs::system::ResMut; @@ -19,150 +12,105 @@ use bevy_input::gamepad::{ use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter}; pub fn gilrs_event_startup_system( - commands: Commands, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, - gamepads: ResMut, - events: EventWriter, -) { - #[cfg(target_arch = "wasm32")] - GILRS.with(|g| { - let g_ref = g.borrow(); - let gilrs = g_ref.as_ref().expect("GILRS was not initialized"); - gilrs_event_startup_system_inner(commands, gilrs, gamepads, events); - }); - #[cfg(not(target_arch = "wasm32"))] - gilrs_event_startup_system_inner(commands, gilrs.0.get(), gamepads, events); -} - -fn gilrs_event_startup_system_inner( mut commands: Commands, - gilrs: &gilrs::Gilrs, + mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, ) { - 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); + gilrs.with(|g| { + for (id, gamepad) in g.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.send(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( - commands: Commands, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, - gamepads: ResMut, - events: EventWriter, - connection_events: EventWriter, - button_events: EventWriter, - axis_event: EventWriter, -) { - #[cfg(target_arch = "wasm32")] - GILRS.with(|g| { - let mut g_ref = g.borrow_mut(); - let gilrs = g_ref.as_mut().expect("GILRS was not initialized"); - gilrs_event_system_inner( - commands, - gilrs, - gamepads, - events, - connection_events, - button_events, - axis_event, - ); + events.send(GamepadConnectionEvent { + gamepad: entity, + connection: GamepadConnection::Connected { + name: gamepad.name().to_string(), + vendor_id: gamepad.vendor_id(), + product_id: gamepad.product_id(), + }, + }); + } }); - #[cfg(not(target_arch = "wasm32"))] - gilrs_event_system_inner( - commands, - gilrs.0.get(), - gamepads, - events, - connection_events, - button_events, - axis_event, - ); } -fn gilrs_event_system_inner( +pub fn gilrs_event_system( mut commands: Commands, - gilrs: &mut gilrs::Gilrs, + mut gilrs: ResMut, mut gamepads: ResMut, mut events: EventWriter, mut connection_events: EventWriter, mut button_events: EventWriter, mut axis_event: EventWriter, ) { - 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 - }); + gilrs.with(|g| { + while let Some(gilrs_event) = g.next_event().filter_ev(&axis_dpad_to_button, g) { + g.update(&gilrs_event); + match gilrs_event.event { + EventType::Connected => { + let pad = g.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(), - }, - ); + let event = GamepadConnectionEvent::new( + entity, + GamepadConnection::Connected { + name: pad.name().to_string(), + vendor_id: pad.vendor_id(), + product_id: pad.product_id(), + }, + ); - events.send(event.clone().into()); - connection_events.send(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.send(event.clone().into()); - connection_events.send(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.send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); - button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); - axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); - } - _ => (), - }; - } - gilrs.inc(); + events.send(event.clone().into()); + connection_events.send(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.send(event.clone().into()); + connection_events.send(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 + .send(RawGamepadButtonChangedEvent::new(gamepad, button, raw_value).into()); + button_events.send(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.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value).into()); + axis_event.send(RawGamepadAxisChangedEvent::new(gamepad, axis, raw_value)); + } + _ => (), + }; + } + g.inc(); + }); } diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index 50adda0e0376b..f994a97bfdc4d 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -34,12 +34,23 @@ use tracing::error; 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. - pub static GILRS: RefCell> = const { RefCell::new(None) }; + static GILRS: RefCell> = const { RefCell::new(None) }; } -#[cfg(not(target_arch = "wasm32"))] #[derive(Resource)] -pub(crate) struct Gilrs(pub SyncCell); +pub(crate) struct Gilrs { + #[cfg(not(target_arch = "wasm32"))] + cell: SyncCell, +} +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)] @@ -78,12 +89,15 @@ impl Plugin for GilrsPlugin { .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)); }); - #[cfg(not(target_arch = "wasm32"))] - app.insert_resource(Gilrs(SyncCell::new(gilrs))); + app.insert_resource(g); app.init_resource::(); app.init_resource::() .add_systems(PreStartup, gilrs_event_startup_system) diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index dd79a702223fd..04233573959b1 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -1,12 +1,5 @@ //! Handle user specified rumble request events. -use crate::GilrsGamepads; - -#[cfg(not(target_arch = "wasm32"))] -use crate::Gilrs; - -#[cfg(target_arch = "wasm32")] -use crate::GILRS; - +use crate::{Gilrs, GilrsGamepads}; use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; use bevy_platform_support::collections::HashMap; @@ -131,60 +124,44 @@ fn handle_rumble_request( Ok(()) } - pub(crate) fn play_gilrs_rumble( time: Res>, - #[cfg(not(target_arch = "wasm32"))] mut gilrs: ResMut, - gamepads: Res, - requests: EventReader, - running_rumbles: ResMut, -) { - #[cfg(target_arch = "wasm32")] - GILRS.with(|g| { - let mut g_ref = g.borrow_mut(); - let gilrs = g_ref.as_mut().expect("GILRS was not initialized"); - play_gilrs_rumble_inner(time, gilrs, gamepads, requests, running_rumbles); - }); - #[cfg(not(target_arch = "wasm32"))] - play_gilrs_rumble_inner(time, gilrs.0.get(), gamepads, requests, running_rumbles); -} - -fn play_gilrs_rumble_inner( - time: Res>, - gilrs: &mut gilrs::Gilrs, + mut gilrs: ResMut, gamepads: Res, mut requests: EventReader, mut running_rumbles: ResMut, ) { - 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}" - ); + gilrs.with(|g| { + 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, g, &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!"); - } - }; - } + Err(RumbleError::GamepadNotFound) => { + warn!("Tried to handle rumble request {gamepad:?} but it doesn't exist!"); + } + }; + } + }); } #[cfg(test)] From fdf099dec8d1baf395bc3d3576fb0884212d1506 Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sat, 22 Feb 2025 10:03:31 -0800 Subject: [PATCH 22/23] Update `GILRS` comment to include more detail --- crates/bevy_gilrs/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index f994a97bfdc4d..db939cf3f0942 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -32,8 +32,13 @@ 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. + /// 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> = const { RefCell::new(None) }; } From a89d19eec64dce00c15e840e39cb0e0e88541aad Mon Sep 17 00:00:00 2001 From: Joshua Holmes Date: Sun, 23 Feb 2025 15:33:58 -0800 Subject: [PATCH 23/23] Replace `g` with `gilrs` for cleaner git diff --- crates/bevy_gilrs/src/gilrs_system.rs | 14 +++++++------- crates/bevy_gilrs/src/rumble.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_gilrs/src/gilrs_system.rs b/crates/bevy_gilrs/src/gilrs_system.rs index 4f9bdbf5ae49b..bd692766ec016 100644 --- a/crates/bevy_gilrs/src/gilrs_system.rs +++ b/crates/bevy_gilrs/src/gilrs_system.rs @@ -17,8 +17,8 @@ pub fn gilrs_event_startup_system( mut gamepads: ResMut, mut events: EventWriter, ) { - gilrs.with(|g| { - for (id, gamepad) in g.gamepads() { + 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); @@ -45,12 +45,12 @@ pub fn gilrs_event_system( mut button_events: EventWriter, mut axis_event: EventWriter, ) { - gilrs.with(|g| { - while let Some(gilrs_event) = g.next_event().filter_ev(&axis_dpad_to_button, g) { - g.update(&gilrs_event); + 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 = g.gamepad(gilrs_event.id); + 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); @@ -111,6 +111,6 @@ pub fn gilrs_event_system( _ => (), }; } - g.inc(); + gilrs.inc(); }); } diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index 04233573959b1..7e49ec77e98c7 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -131,7 +131,7 @@ pub(crate) fn play_gilrs_rumble( mut requests: EventReader, mut running_rumbles: ResMut, ) { - gilrs.with(|g| { + gilrs.with(|gilrs| { let current_time = time.elapsed(); // Remove outdated rumble effects. for rumbles in running_rumbles.rumbles.values_mut() { @@ -145,7 +145,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, g, &gamepads, rumble, current_time) { + match handle_rumble_request(&mut running_rumbles, gilrs, &gamepads, rumble, current_time) { Ok(()) => {} Err(RumbleError::GilrsError(err)) => { if let ff::Error::FfNotSupported(_) = err {