feat: implement event handlers for OpenXR events so that plugins can handle events without needing to modify bevy_mod_openxr

Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
Schmarni
2024-11-29 23:45:02 +01:00
parent 58db782997
commit 5239700eca
4 changed files with 130 additions and 57 deletions

View File

@@ -1,12 +1,13 @@
use std::{mem, ptr}; use std::{mem, ptr};
use bevy::prelude::*; use bevy::prelude::*;
use openxr::sys; use openxr::{sys, Event};
use crate::{ use crate::{
next_chain::{OxrNextChainStructBase, OxrNextChainStructProvider}, next_chain::{OxrNextChainStructBase, OxrNextChainStructProvider},
openxr::exts::OxrEnabledExtensions, openxr::exts::OxrEnabledExtensions,
openxr_session_available, openxr_session_available,
poll_events::{OxrEvent, OxrEventHandlerExt},
session::{OxrSessionCreateNextChain, OxrSessionCreateNextProvider}, session::{OxrSessionCreateNextChain, OxrSessionCreateNextProvider},
}; };
@@ -20,6 +21,16 @@ impl Plugin for OxrOverlayPlugin {
First, First,
add_overlay_info_to_chain.run_if(openxr_session_available), add_overlay_info_to_chain.run_if(openxr_session_available),
); );
app.add_oxr_event_handler(handle_overlay_event);
}
}
fn handle_overlay_event(event: In<OxrEvent>, mut writer: EventWriter<OxrOverlaySessionEvent>) {
if let Event::MainSessionVisibilityChangedEXTX(event) = unsafe { event.get() } {
writer.send(OxrOverlaySessionEvent::MainSessionVisibilityChanged {
visible: event.visible(),
flags: event.flags(),
});
} }
} }

View File

@@ -18,6 +18,7 @@ use bevy::winit::UpdateMode;
use bevy::winit::WinitSettings; use bevy::winit::WinitSettings;
use bevy_mod_xr::session::*; use bevy_mod_xr::session::*;
use openxr::Event; use openxr::Event;
use openxr::EventDataBuffer;
use crate::error::OxrError; use crate::error::OxrError;
use crate::features::overlay::OxrOverlaySessionEvent; use crate::features::overlay::OxrOverlaySessionEvent;
@@ -28,6 +29,8 @@ use crate::session::OxrSessionCreateNextChain;
use crate::types::*; use crate::types::*;
use super::exts::OxrEnabledExtensions; use super::exts::OxrEnabledExtensions;
use super::poll_events::OxrEvent;
use super::poll_events::OxrEventHandlerExt;
pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool { pub fn session_started(started: Option<Res<OxrSessionStarted>>) -> bool {
started.is_some_and(|started| started.0) started.is_some_and(|started| started.0)
@@ -106,12 +109,7 @@ impl Plugin for OxrInitPlugin {
}, },
ExtractResourcePlugin::<OxrSessionStarted>::default(), ExtractResourcePlugin::<OxrSessionStarted>::default(),
)) ))
.add_systems( .add_oxr_event_handler(handle_events)
XrFirst,
poll_events
.in_set(XrHandleEvents::Poll)
.run_if(not(state_equals(XrState::Unavailable))),
)
.add_systems( .add_systems(
XrFirst, XrFirst,
( (
@@ -282,65 +280,46 @@ impl OxrInitPlugin {
#[derive(Event, Clone, Copy, Debug, Default)] #[derive(Event, Clone, Copy, Debug, Default)]
pub struct OxrInteractionProfileChanged; pub struct OxrInteractionProfileChanged;
/// Polls any OpenXR events and handles them accordingly pub fn handle_events(
pub fn poll_events( event: In<OxrEvent>,
instance: Res<OxrInstance>,
mut status: ResMut<XrState>, mut status: ResMut<XrState>,
mut changed_event: EventWriter<XrStateChanged>, mut changed_event: EventWriter<XrStateChanged>,
mut interaction_profile_changed_event: EventWriter<OxrInteractionProfileChanged>, mut interaction_profile_changed_event: EventWriter<OxrInteractionProfileChanged>,
mut overlay_writer: Option<ResMut<Events<OxrOverlaySessionEvent>>>,
) { ) {
let _span = info_span!("xr_poll_events"); use openxr::Event::*;
let mut buffer = Default::default(); match unsafe { event.get() } {
while let Some(event) = instance SessionStateChanged(state) => {
.poll_event(&mut buffer) use openxr::SessionState;
.expect("Failed to poll event")
{
use openxr::Event::*;
match event {
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 { let new_status = match state {
SessionState::IDLE => XrState::Idle, SessionState::IDLE => XrState::Idle,
SessionState::READY => XrState::Ready, SessionState::READY => XrState::Ready,
SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => { SessionState::SYNCHRONIZED | SessionState::VISIBLE | SessionState::FOCUSED => {
XrState::Running 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!");
} }
} SessionState::STOPPING => XrState::Stopping,
// we might want to check if this is the correct session? SessionState::EXITING => XrState::Exiting {
Event::InteractionProfileChanged(_) => { should_restart: false,
interaction_profile_changed_event.send_default(); },
} 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();
}
_ => {}
} }
} }

View File

@@ -9,6 +9,7 @@ use bevy::{
use bevy_mod_xr::session::XrSessionPlugin; use bevy_mod_xr::session::XrSessionPlugin;
use bevy_mod_xr::{camera::XrCameraPlugin, session::XrState}; use bevy_mod_xr::{camera::XrCameraPlugin, session::XrState};
use init::OxrInitPlugin; use init::OxrInitPlugin;
use poll_events::{OxrEventHandlers, OxrEventsPlugin};
use render::OxrRenderPlugin; use render::OxrRenderPlugin;
use resources::OxrInstance; use resources::OxrInstance;
use session::OxrSession; use session::OxrSession;
@@ -35,6 +36,7 @@ pub mod resources;
pub mod session; pub mod session;
pub mod spaces; pub mod spaces;
pub mod types; pub mod types;
pub mod poll_events;
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the OpenXR session is available. /// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the OpenXR session is available.
pub fn openxr_session_available( pub fn openxr_session_available(
@@ -60,6 +62,7 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
// .disable::<PipelinedRenderingPlugin>() // .disable::<PipelinedRenderingPlugin>()
.add_before::<RenderPlugin, _>(XrSessionPlugin { auto_handle: true }) .add_before::<RenderPlugin, _>(XrSessionPlugin { auto_handle: true })
.add_before::<RenderPlugin, _>(OxrInitPlugin::default()) .add_before::<RenderPlugin, _>(OxrInitPlugin::default())
.add(OxrEventsPlugin)
.add(OxrReferenceSpacePlugin::default()) .add(OxrReferenceSpacePlugin::default())
.add(OxrRenderPlugin) .add(OxrRenderPlugin)
.add(OxrPassthroughPlugin) .add(OxrPassthroughPlugin)

View File

@@ -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::<OxrEventHandlers>();
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::<OxrInstance>().clone();
let handlers = world.remove_resource::<OxrEventHandlers>().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<OxrEventHandler>,
}
pub type OxrEventHandler = SystemId<OxrEvent, ()>;
pub struct OxrEvent {
event: Event<'static>,
}
impl OxrEvent {
pub(crate) fn new<'a>(event: Event<'a>) -> Self {
Self {
event: unsafe { mem::transmute::<Event<'a>, 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<M>(
&mut self,
system: impl IntoSystem<OxrEvent, (), M> + 'static,
) -> &mut Self;
}
impl OxrEventHandlerExt for App {
fn add_oxr_event_handler<M>(&mut self, system: impl IntoSystem<OxrEvent, (), M> + 'static) -> &mut Self {
self.init_resource::<OxrEventHandlers>();
let id = self.register_system(system);
self.world_mut()
.resource_mut::<OxrEventHandlers>()
.handlers
.push(id);
self
}
}