use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use bevy::app::{AppExit, MainScheduleOrder};
use bevy::ecs::schedule::ScheduleLabel;
use bevy::prelude::*;
use bevy::render::extract_resource::{ExtractResource, ExtractResourcePlugin};
use bevy::render::{Render, RenderApp, RenderSet};
#[derive(Event, Clone, Copy, Default)]
pub struct XrCreateSessionEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrSessionCreated;
#[derive(Event, Clone, Copy, Default)]
pub struct XrSessionCreatedEvent;
#[derive(Event, Clone, Copy, Default)]
pub struct XrDestroySessionEvent;
#[derive(Resource, Clone, Default)]
pub struct XrDestroySessionRender(pub Arc<AtomicBool>);
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrPreDestroySession;
#[derive(Event, Clone, Copy, Default)]
pub struct XrBeginSessionEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrPostSessionBegin;
#[derive(Event, Clone, Copy, Default)]
pub struct XrEndSessionEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrPreSessionEnd;
#[derive(Event, Clone, Copy, Default)]
pub struct XrRequestExitEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, ScheduleLabel)]
pub struct XrFirst;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum XrHandleEvents {
Poll,
ExitEvents,
SessionStateUpdateEvents,
Cleanup,
FrameLoop,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum XrRenderSet {
HandleEvents,
PreRender,
PostRender,
}
#[derive(ExtractResource, Resource, Clone, Copy, Default)]
pub struct XrRootTransform(pub GlobalTransform);
#[derive(Component)]
pub struct XrTrackingRoot;
pub struct XrSessionPlugin {
pub auto_handle: bool,
}
impl Plugin for XrSessionPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<XrDestroySessionRender>();
let mut xr_first = Schedule::new(XrFirst);
xr_first.set_executor_kind(bevy::ecs::schedule::ExecutorKind::Simple);
app.add_event::<XrCreateSessionEvent>()
.add_event::<XrDestroySessionEvent>()
.add_event::<XrBeginSessionEvent>()
.add_event::<XrEndSessionEvent>()
.add_event::<XrRequestExitEvent>()
.add_event::<XrStateChanged>()
.add_event::<XrSessionCreatedEvent>()
.init_schedule(XrSessionCreated)
.init_schedule(XrPreDestroySession)
.init_schedule(XrPostSessionBegin)
.init_schedule(XrPreSessionEnd)
.add_schedule(xr_first)
.configure_sets(
XrFirst,
(
XrHandleEvents::Poll,
XrHandleEvents::ExitEvents,
XrHandleEvents::SessionStateUpdateEvents,
XrHandleEvents::Cleanup,
XrHandleEvents::FrameLoop,
)
.chain(),
)
.add_systems(
XrFirst,
exits_session_on_app_exit
.run_if(on_event::<AppExit>())
.run_if(session_created)
.in_set(XrHandleEvents::ExitEvents),
);
app.world_mut()
.resource_mut::<MainScheduleOrder>()
.labels
.insert(0, XrFirst.intern());
if self.auto_handle {
app.add_systems(PreUpdate, auto_handle_session);
}
}
fn finish(&self, app: &mut App) {
if app.get_sub_app(RenderApp).is_none() {
return;
}
app.add_plugins((
ExtractResourcePlugin::<XrState>::default(),
ExtractResourcePlugin::<XrRootTransform>::default(),
))
.init_resource::<XrRootTransform>()
.add_systems(
PostUpdate,
update_root_transform.after(TransformSystem::TransformPropagate),
)
.add_systems(
XrFirst,
exits_session_on_app_exit
.before(XrHandleEvents::ExitEvents)
.run_if(on_event::<AppExit>().and_then(session_running)),
);
let render_app = app.sub_app_mut(RenderApp);
render_app
.init_schedule(XrPreDestroySession)
.init_resource::<XrRootTransform>()
.configure_sets(
Render,
(XrRenderSet::HandleEvents, XrRenderSet::PreRender).chain(),
)
.configure_sets(
Render,
XrRenderSet::HandleEvents.after(RenderSet::ExtractCommands),
)
.configure_sets(
Render,
XrRenderSet::PreRender
.before(RenderSet::ManageViews)
.before(RenderSet::PrepareAssets),
)
.configure_sets(
Render,
XrRenderSet::PostRender
.after(RenderSet::Render)
.before(RenderSet::Cleanup),
);
}
}
fn exits_session_on_app_exit(mut request_exit: EventWriter<XrRequestExitEvent>) {
request_exit.send_default();
}
#[derive(Event, Clone, Copy, Deref)]
pub struct XrStateChanged(pub XrState);
#[derive(Clone, Copy, Debug, ExtractResource, Resource, PartialEq, Eq)]
#[repr(u8)]
pub enum XrState {
Unavailable,
Available,
Idle,
Ready,
Running,
Stopping,
Exiting {
should_restart: bool,
},
}
pub fn auto_handle_session(
mut state_changed: EventReader<XrStateChanged>,
mut create_session: EventWriter<XrCreateSessionEvent>,
mut begin_session: EventWriter<XrBeginSessionEvent>,
mut end_session: EventWriter<XrEndSessionEvent>,
mut destroy_session: EventWriter<XrDestroySessionEvent>,
mut no_auto_restart: Local<bool>,
) {
for XrStateChanged(state) in state_changed.read() {
match state {
XrState::Available => {
if !*no_auto_restart {
create_session.send_default();
}
}
XrState::Ready => {
begin_session.send_default();
}
XrState::Stopping => {
end_session.send_default();
}
XrState::Exiting { should_restart } => {
*no_auto_restart = !should_restart;
destroy_session.send_default();
}
_ => (),
}
}
}
pub fn update_root_transform(
mut root_transform: ResMut<XrRootTransform>,
root: Query<&GlobalTransform, With<XrTrackingRoot>>,
) {
let Ok(transform) = root.get_single() else {
return;
};
root_transform.0 = *transform;
}
pub fn status_changed_to(
status: XrState,
) -> impl FnMut(EventReader<XrStateChanged>) -> bool + Clone {
move |mut reader: EventReader<XrStateChanged>| {
reader.read().any(|new_status| new_status.0 == status)
}
}
pub fn session_available(status: Option<Res<XrState>>) -> bool {
status.is_some_and(|s| *s != XrState::Unavailable)
}
pub fn session_created(status: Option<Res<XrState>>) -> bool {
!matches!(
status.as_deref(),
Some(XrState::Unavailable | XrState::Available) | None
)
}
pub fn session_ready_or_running(status: Option<Res<XrState>>) -> bool {
matches!(status.as_deref(), Some(XrState::Ready | XrState::Running))
}
pub fn session_running(status: Option<Res<XrState>>) -> bool {
matches!(status.as_deref(), Some(XrState::Running))
}
pub fn state_equals(status: XrState) -> impl FnMut(Option<Res<XrState>>) -> bool {
move |state: Option<Res<XrState>>| state.is_some_and(|s| *s == status)
}
#[macro_export]
macro_rules! state_matches {
($match:pat) => {
|state: Option<Res<XrState>>| core::matches!(state.as_deref(), Some($match))
};
}
use bevy::transform::TransformSystem;
pub use state_matches;