From 5239700eca277b9ef37443702c33caf1beb2f152 Mon Sep 17 00:00:00 2001 From: Schmarni Date: Fri, 29 Nov 2024 23:45:02 +0100 Subject: [PATCH 1/2] feat: implement event handlers for OpenXR events so that plugins can handle events without needing to modify bevy_mod_openxr Signed-off-by: Schmarni --- .../src/openxr/features/overlay.rs | 13 ++- crates/bevy_openxr/src/openxr/init.rs | 91 +++++++------------ crates/bevy_openxr/src/openxr/mod.rs | 3 + crates/bevy_openxr/src/openxr/poll_events.rs | 80 ++++++++++++++++ 4 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 crates/bevy_openxr/src/openxr/poll_events.rs diff --git a/crates/bevy_openxr/src/openxr/features/overlay.rs b/crates/bevy_openxr/src/openxr/features/overlay.rs index 7b3d072..3d5ea63 100644 --- a/crates/bevy_openxr/src/openxr/features/overlay.rs +++ b/crates/bevy_openxr/src/openxr/features/overlay.rs @@ -1,12 +1,13 @@ use std::{mem, ptr}; use bevy::prelude::*; -use openxr::sys; +use openxr::{sys, Event}; use crate::{ next_chain::{OxrNextChainStructBase, OxrNextChainStructProvider}, openxr::exts::OxrEnabledExtensions, openxr_session_available, + poll_events::{OxrEvent, OxrEventHandlerExt}, session::{OxrSessionCreateNextChain, OxrSessionCreateNextProvider}, }; @@ -20,6 +21,16 @@ impl Plugin for OxrOverlayPlugin { First, add_overlay_info_to_chain.run_if(openxr_session_available), ); + app.add_oxr_event_handler(handle_overlay_event); + } +} + +fn handle_overlay_event(event: In, mut writer: EventWriter) { + if let Event::MainSessionVisibilityChangedEXTX(event) = unsafe { event.get() } { + writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged { + visible: event.visible(), + flags: event.flags(), + }); } } diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index fa8d4fd..e0a8912 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -18,6 +18,7 @@ use bevy::winit::UpdateMode; use bevy::winit::WinitSettings; use bevy_mod_xr::session::*; use openxr::Event; +use openxr::EventDataBuffer; use crate::error::OxrError; use crate::features::overlay::OxrOverlaySessionEvent; @@ -28,6 +29,8 @@ use crate::session::OxrSessionCreateNextChain; use crate::types::*; use super::exts::OxrEnabledExtensions; +use super::poll_events::OxrEvent; +use super::poll_events::OxrEventHandlerExt; pub fn session_started(started: Option>) -> bool { started.is_some_and(|started| started.0) @@ -106,12 +109,7 @@ impl Plugin for OxrInitPlugin { }, ExtractResourcePlugin::::default(), )) - .add_systems( - XrFirst, - poll_events - .in_set(XrHandleEvents::Poll) - .run_if(not(state_equals(XrState::Unavailable))), - ) + .add_oxr_event_handler(handle_events) .add_systems( XrFirst, ( @@ -282,65 +280,46 @@ impl OxrInitPlugin { #[derive(Event, Clone, Copy, Debug, Default)] pub struct OxrInteractionProfileChanged; -/// Polls any OpenXR events and handles them accordingly -pub fn poll_events( - instance: Res, +pub fn handle_events( + event: In, mut status: ResMut, mut changed_event: EventWriter, mut interaction_profile_changed_event: EventWriter, - mut overlay_writer: Option>>, ) { - let _span = info_span!("xr_poll_events"); - let mut buffer = Default::default(); - while let Some(event) = instance - .poll_event(&mut buffer) - .expect("Failed to poll event") - { - use openxr::Event::*; - match event { - SessionStateChanged(state) => { - use openxr::SessionState; + use openxr::Event::*; + match unsafe { event.get() } { + SessionStateChanged(state) => { + use openxr::SessionState; - let state = state.state(); + let state = state.state(); - info!("entered XR state {:?}", state); + info!("entered XR state {:?}", state); - let new_status = match state { - SessionState::IDLE => XrState::Idle, - SessionState::READY => XrState::Ready, - SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { - XrState::Running - } - SessionState::STOPPING => XrState::Stopping, - SessionState::EXITING => XrState::Exiting { - should_restart: false, - }, - SessionState::LOSS_PENDING => XrState::Exiting { - should_restart: true, - }, - _ => unreachable!(), - }; - changed_event.send(XrStateChanged(new_status)); - *status = new_status; - } - InstanceLossPending(_) => {} - EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()), - MainSessionVisibilityChangedEXTX(d) => { - if let Some(writer) = overlay_writer.as_mut() { - writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged { - visible: d.visible(), - flags: d.flags(), - }); - } else { - warn!("Overlay Event Recieved without the OverlayPlugin being added!"); + let new_status = match state { + SessionState::IDLE => XrState::Idle, + SessionState::READY => XrState::Ready, + SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { + XrState::Running } - } - // we might want to check if this is the correct session? - Event::InteractionProfileChanged(_) => { - interaction_profile_changed_event.send_default(); - } - _ => {} + SessionState::STOPPING => XrState::Stopping, + SessionState::EXITING => XrState::Exiting { + should_restart: false, + }, + SessionState::LOSS_PENDING => XrState::Exiting { + should_restart: true, + }, + _ => unreachable!(), + }; + changed_event.send(XrStateChanged(new_status)); + *status = new_status; } + InstanceLossPending(_) => {} + EventsLost(e) => warn!("lost {} XR events", e.lost_event_count()), + // we might want to check if this is the correct session? + Event::InteractionProfileChanged(_) => { + interaction_profile_changed_event.send_default(); + } + _ => {} } } diff --git a/crates/bevy_openxr/src/openxr/mod.rs b/crates/bevy_openxr/src/openxr/mod.rs index d01c0a1..a9f225e 100644 --- a/crates/bevy_openxr/src/openxr/mod.rs +++ b/crates/bevy_openxr/src/openxr/mod.rs @@ -9,6 +9,7 @@ use bevy::{ use bevy_mod_xr::session::XrSessionPlugin; use bevy_mod_xr::{camera::XrCameraPlugin, session::XrState}; use init::OxrInitPlugin; +use poll_events::{OxrEventHandlers, OxrEventsPlugin}; use render::OxrRenderPlugin; use resources::OxrInstance; use session::OxrSession; @@ -35,6 +36,7 @@ pub mod resources; pub mod session; pub mod spaces; pub mod types; +pub mod poll_events; /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the OpenXR session is available. pub fn openxr_session_available( @@ -60,6 +62,7 @@ pub fn add_xr_plugins(plugins: G) -> PluginGroupBuilder { // .disable::() .add_before::(XrSessionPlugin { auto_handle: true }) .add_before::(OxrInitPlugin::default()) + .add(OxrEventsPlugin) .add(OxrReferenceSpacePlugin::default()) .add(OxrRenderPlugin) .add(OxrPassthroughPlugin) diff --git a/crates/bevy_openxr/src/openxr/poll_events.rs b/crates/bevy_openxr/src/openxr/poll_events.rs new file mode 100644 index 0000000..029bd6b --- /dev/null +++ b/crates/bevy_openxr/src/openxr/poll_events.rs @@ -0,0 +1,80 @@ +use std::mem; + +use bevy::{ecs::system::SystemId, prelude::*}; +use bevy_mod_xr::session::{XrFirst, XrHandleEvents}; +use openxr::{Event, EventDataBuffer}; + +pub struct OxrEventsPlugin; + +impl Plugin for OxrEventsPlugin { + fn build(&self, app: &mut App) { + app.init_resource::(); + app.add_systems( + XrFirst, + poll_events + .in_set(XrHandleEvents::Poll) + .run_if(openxr_session_available), + ); + } +} +/// Polls any OpenXR events and handles them accordingly +pub fn poll_events(world: &mut World) { + let _span = info_span!("xr_poll_events"); + let instance = world.resource::().clone(); + let handlers = world.remove_resource::().unwrap(); + let mut buffer = EventDataBuffer::default(); + while let Some(event) = instance + .poll_event(&mut buffer) + .expect("Failed to poll event") + { + for handler in handlers.handlers.iter() { + if let Err(err) = world.run_system_with_input::<_, ()>(*handler, OxrEvent::new(event)) { + error!("error when running oxr event handler: {err}"); + }; + } + } + world.insert_resource(handlers); +} + +use super::{openxr_session_available, resources::OxrInstance}; +#[derive(Resource, Debug, Default)] +pub struct OxrEventHandlers { + pub handlers: Vec, +} +pub type OxrEventHandler = SystemId; + +pub struct OxrEvent { + event: Event<'static>, +} + +impl OxrEvent { + pub(crate) fn new<'a>(event: Event<'a>) -> Self { + Self { + event: unsafe { mem::transmute::, Event<'static>>(event) }, + } + } + /// # Safety + /// The event is only valid for the duration of the poll event callback, + /// don't Store the [Event] anywhere!! + #[allow(clippy::needless_lifetimes)] + pub unsafe fn get<'a>(&'a self) -> Event<'a> { + self.event + } +} +pub trait OxrEventHandlerExt { + fn add_oxr_event_handler( + &mut self, + system: impl IntoSystem + 'static, + ) -> &mut Self; +} +impl OxrEventHandlerExt for App { + fn add_oxr_event_handler(&mut self, system: impl IntoSystem + 'static) -> &mut Self { + self.init_resource::(); + let id = self.register_system(system); + self.world_mut() + .resource_mut::() + .handlers + .push(id); + self + } +} From bc0c1c472bdffaa636ad8d436f2fbc390861d9ea Mon Sep 17 00:00:00 2001 From: Schmarni Date: Sat, 30 Nov 2024 01:16:17 +0100 Subject: [PATCH 2/2] make OxrEvent less unsound Signed-off-by: Schmarni --- .../src/openxr/features/overlay.rs | 3 ++- crates/bevy_openxr/src/openxr/init.rs | 5 ++-- crates/bevy_openxr/src/openxr/mod.rs | 4 ++-- crates/bevy_openxr/src/openxr/poll_events.rs | 24 ++++++++++++------- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/crates/bevy_openxr/src/openxr/features/overlay.rs b/crates/bevy_openxr/src/openxr/features/overlay.rs index 3d5ea63..dfca5d3 100644 --- a/crates/bevy_openxr/src/openxr/features/overlay.rs +++ b/crates/bevy_openxr/src/openxr/features/overlay.rs @@ -26,7 +26,8 @@ impl Plugin for OxrOverlayPlugin { } fn handle_overlay_event(event: In, mut writer: EventWriter) { - if let Event::MainSessionVisibilityChangedEXTX(event) = unsafe { event.get() } { + // this unwrap will never panic since we are in a valid scope + if let Event::MainSessionVisibilityChangedEXTX(event) = unsafe { event.get() }.unwrap() { writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged { visible: event.visible(), flags: event.flags(), diff --git a/crates/bevy_openxr/src/openxr/init.rs b/crates/bevy_openxr/src/openxr/init.rs index e0a8912..5567746 100644 --- a/crates/bevy_openxr/src/openxr/init.rs +++ b/crates/bevy_openxr/src/openxr/init.rs @@ -18,10 +18,8 @@ use bevy::winit::UpdateMode; use bevy::winit::WinitSettings; use bevy_mod_xr::session::*; use openxr::Event; -use openxr::EventDataBuffer; use crate::error::OxrError; -use crate::features::overlay::OxrOverlaySessionEvent; use crate::graphics::*; use crate::resources::*; use crate::session::OxrSession; @@ -287,7 +285,8 @@ pub fn handle_events( mut interaction_profile_changed_event: EventWriter, ) { use openxr::Event::*; - match unsafe { event.get() } { + // this unwrap will never panic since we are in a valid scope + match unsafe { event.get() }.unwrap() { SessionStateChanged(state) => { use openxr::SessionState; diff --git a/crates/bevy_openxr/src/openxr/mod.rs b/crates/bevy_openxr/src/openxr/mod.rs index a9f225e..85c5c81 100644 --- a/crates/bevy_openxr/src/openxr/mod.rs +++ b/crates/bevy_openxr/src/openxr/mod.rs @@ -9,7 +9,7 @@ use bevy::{ use bevy_mod_xr::session::XrSessionPlugin; use bevy_mod_xr::{camera::XrCameraPlugin, session::XrState}; use init::OxrInitPlugin; -use poll_events::{OxrEventHandlers, OxrEventsPlugin}; +use poll_events::OxrEventsPlugin; use render::OxrRenderPlugin; use resources::OxrInstance; use session::OxrSession; @@ -30,13 +30,13 @@ pub mod helper_traits; pub mod init; pub mod layer_builder; pub mod next_chain; +pub mod poll_events; pub mod reference_space; pub mod render; pub mod resources; pub mod session; pub mod spaces; pub mod types; -pub mod poll_events; /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the OpenXR session is available. pub fn openxr_session_available( diff --git a/crates/bevy_openxr/src/openxr/poll_events.rs b/crates/bevy_openxr/src/openxr/poll_events.rs index 029bd6b..4b4d696 100644 --- a/crates/bevy_openxr/src/openxr/poll_events.rs +++ b/crates/bevy_openxr/src/openxr/poll_events.rs @@ -1,4 +1,4 @@ -use std::mem; +use std::{cell::RefCell, mem, ops::Deref, rc::Rc}; use bevy::{ecs::system::SystemId, prelude::*}; use bevy_mod_xr::session::{XrFirst, XrHandleEvents}; @@ -27,11 +27,15 @@ pub fn poll_events(world: &mut World) { .poll_event(&mut buffer) .expect("Failed to poll event") { + let event = Rc::new(RefCell::new(Some(event))); for handler in handlers.handlers.iter() { - if let Err(err) = world.run_system_with_input::<_, ()>(*handler, OxrEvent::new(event)) { + if let Err(err) = + world.run_system_with_input::<_, ()>(*handler, OxrEvent::new(event.clone())) + { error!("error when running oxr event handler: {err}"); }; } + event.deref().take(); } world.insert_resource(handlers); } @@ -44,21 +48,22 @@ pub struct OxrEventHandlers { pub type OxrEventHandler = SystemId; pub struct OxrEvent { - event: Event<'static>, + event: Rc>>>, } impl OxrEvent { - pub(crate) fn new<'a>(event: Event<'a>) -> Self { + pub(crate) fn new<'a>(event: Rc>>>) -> Self { Self { - event: unsafe { mem::transmute::, Event<'static>>(event) }, + event: unsafe { mem::transmute(event) }, } } + /// always returns [Some] if called in a valid scope /// # Safety /// The event is only valid for the duration of the poll event callback, /// don't Store the [Event] anywhere!! #[allow(clippy::needless_lifetimes)] - pub unsafe fn get<'a>(&'a self) -> Event<'a> { - self.event + pub unsafe fn get<'a>(&'a self) -> Option> { + self.event.borrow().clone() } } pub trait OxrEventHandlerExt { @@ -68,7 +73,10 @@ pub trait OxrEventHandlerExt { ) -> &mut Self; } impl OxrEventHandlerExt for App { - fn add_oxr_event_handler(&mut self, system: impl IntoSystem + 'static) -> &mut Self { + fn add_oxr_event_handler( + &mut self, + system: impl IntoSystem + 'static, + ) -> &mut Self { self.init_resource::(); let id = self.register_system(system); self.world_mut()