Merge pull request #98 from Schmarni-Dev/action-binding-and-attaching-systems
Refactor - Generic system for binding sugesstion and action set attaching
This commit is contained in:
103
crates/bevy_openxr/src/openxr/action_binding.rs
Normal file
103
crates/bevy_openxr/src/openxr/action_binding.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use bevy::ecs::schedule::ScheduleLabel;
|
||||||
|
use bevy::ecs::system::RunSystemOnce;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
|
use bevy_xr::session::{status_changed_to, XrStatus};
|
||||||
|
use openxr::sys::ActionSuggestedBinding;
|
||||||
|
|
||||||
|
use crate::resources::OxrInstance;
|
||||||
|
|
||||||
|
impl Plugin for OxrActionBindingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_schedule(Schedule::new(OxrSendActionBindings));
|
||||||
|
app.add_event::<OxrSuggestActionBinding>();
|
||||||
|
app.add_systems(
|
||||||
|
Update,
|
||||||
|
run_action_binding_sugestion
|
||||||
|
.run_if(status_changed_to(XrStatus::Ready).and_then(run_once())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This could for now be handled better with a SystemSet, but in the future we might want to add an
|
||||||
|
// Event to allow requesting binding suggestion for new actions
|
||||||
|
fn run_action_binding_sugestion(world: &mut World) {
|
||||||
|
world.run_schedule(OxrSendActionBindings);
|
||||||
|
world.run_system_once(bind_actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_actions(instance: Res<OxrInstance>, mut actions: EventReader<OxrSuggestActionBinding>) {
|
||||||
|
let mut bindings: HashMap<&str, Vec<ActionSuggestedBinding>> = HashMap::new();
|
||||||
|
for e in actions.read() {
|
||||||
|
bindings.entry(&e.interaction_profile).or_default().extend(
|
||||||
|
e.bindings
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|b| match instance.string_to_path(&b) {
|
||||||
|
Ok(p) => Some(p),
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Unable to convert path: \"{}\"; error: {}",
|
||||||
|
b,
|
||||||
|
err.to_string()
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|p| ActionSuggestedBinding {
|
||||||
|
action: e.action,
|
||||||
|
binding: p,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
use openxr::sys;
|
||||||
|
for (profile, bindings) in bindings.iter() {
|
||||||
|
let interaction_profile = match instance.string_to_path(profile) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Unable to convert interaction profile path: \"{}\"; error: \"{}\" Skipping all suggestions for this interaction profile",
|
||||||
|
profile,
|
||||||
|
err.to_string()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Using the raw way since we want all actions through one event and we can't use the
|
||||||
|
// Bindings from the openxr crate since they can't be created from raw actions
|
||||||
|
let info = sys::InteractionProfileSuggestedBinding {
|
||||||
|
ty: sys::InteractionProfileSuggestedBinding::TYPE,
|
||||||
|
next: ptr::null(),
|
||||||
|
interaction_profile,
|
||||||
|
count_suggested_bindings: bindings.len() as u32,
|
||||||
|
suggested_bindings: bindings.as_ptr() as *const _ as _,
|
||||||
|
};
|
||||||
|
match unsafe {
|
||||||
|
(instance.fp().suggest_interaction_profile_bindings)(instance.as_raw(), &info)
|
||||||
|
} {
|
||||||
|
openxr::sys::Result::ERROR_ACTIONSETS_ALREADY_ATTACHED => error!(
|
||||||
|
"Binding Suggested for an Action whith an ActionSet that was already attached!"
|
||||||
|
),
|
||||||
|
openxr::sys::Result::ERROR_PATH_INVALID => error!("Invalid Path Suggested!"),
|
||||||
|
openxr::sys::Result::ERROR_PATH_UNSUPPORTED => error!("Suggested Path Unsupported!"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event, Clone)]
|
||||||
|
/// Only Send this for Actions that were not attached yet!
|
||||||
|
pub struct OxrSuggestActionBinding {
|
||||||
|
pub action: openxr::sys::Action,
|
||||||
|
pub interaction_profile: Cow<'static, str>,
|
||||||
|
pub bindings: Vec<Cow<'static, str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OxrActionBindingPlugin;
|
||||||
|
// Maybe use a SystemSet in an XrStartup Schedule?
|
||||||
|
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct OxrSendActionBindings;
|
||||||
37
crates/bevy_openxr/src/openxr/action_set_attaching.rs
Normal file
37
crates/bevy_openxr/src/openxr/action_set_attaching.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::resources::OxrSession;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_xr::session::status_changed_to;
|
||||||
|
|
||||||
|
impl Plugin for OxrActionAttachingPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_event::<OxrAttachActionSet>();
|
||||||
|
app.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
attach_sets.run_if(status_changed_to(bevy_xr::session::XrStatus::Ready)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attach_sets(session: Res<OxrSession>, mut events: EventReader<OxrAttachActionSet>) {
|
||||||
|
let sets = events.read().map(|v| &v.0).collect::<Vec<_>>();
|
||||||
|
info!("attaching {} sessions", sets.len());
|
||||||
|
match session.attach_action_sets(&sets) {
|
||||||
|
Ok(_) => {info!("attached sessions!")}
|
||||||
|
Err(openxr::sys::Result::ERROR_ACTIONSETS_ALREADY_ATTACHED) => {
|
||||||
|
error!("Action Sets Already attached!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(openxr::sys::Result::ERROR_HANDLE_INVALID) => error!("Invalid ActionSet Handle!"),
|
||||||
|
Err(e) => error!(
|
||||||
|
"Unhandled Error while attaching action sets: {}",
|
||||||
|
e.to_string()
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event, Clone)]
|
||||||
|
/// Send this event for every ActionSet you want to attach to the [`OxrSession`] once the Session Status changed to Ready. all requests will
|
||||||
|
/// be applied in [`PostUpdate`]
|
||||||
|
pub struct OxrAttachActionSet(pub openxr::ActionSet);
|
||||||
|
|
||||||
|
pub struct OxrActionAttachingPlugin;
|
||||||
@@ -24,6 +24,7 @@ use bevy_xr::session::DestroyXrSession;
|
|||||||
use bevy_xr::session::EndXrSession;
|
use bevy_xr::session::EndXrSession;
|
||||||
use bevy_xr::session::XrSharedStatus;
|
use bevy_xr::session::XrSharedStatus;
|
||||||
use bevy_xr::session::XrStatus;
|
use bevy_xr::session::XrStatus;
|
||||||
|
use bevy_xr::session::XrStatusChanged;
|
||||||
|
|
||||||
use crate::error::OxrError;
|
use crate::error::OxrError;
|
||||||
use crate::graphics::*;
|
use crate::graphics::*;
|
||||||
@@ -334,17 +335,15 @@ fn init_xr_session(
|
|||||||
|
|
||||||
preferred
|
preferred
|
||||||
} else {
|
} else {
|
||||||
if let Some(config) = view_configuration_views.first() {
|
view_configuration_views.first().map(|config| {
|
||||||
Some((
|
(
|
||||||
UVec2::new(
|
UVec2::new(
|
||||||
config.recommended_image_rect_width,
|
config.recommended_image_rect_width,
|
||||||
config.recommended_image_rect_height,
|
config.recommended_image_rect_height,
|
||||||
),
|
),
|
||||||
*config,
|
*config,
|
||||||
))
|
)
|
||||||
} else {
|
})
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.ok_or(OxrError::NoAvailableViewConfiguration)?;
|
.ok_or(OxrError::NoAvailableViewConfiguration)?;
|
||||||
|
|
||||||
@@ -499,7 +498,11 @@ pub fn transfer_xr_resources(mut commands: Commands, mut world: ResMut<MainWorld
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Polls any OpenXR events and handles them accordingly
|
/// Polls any OpenXR events and handles them accordingly
|
||||||
pub fn poll_events(instance: Res<OxrInstance>, status: Res<XrSharedStatus>) {
|
pub fn poll_events(
|
||||||
|
instance: Res<OxrInstance>,
|
||||||
|
status: Res<XrSharedStatus>,
|
||||||
|
mut changed_event: EventWriter<XrStatusChanged>,
|
||||||
|
) {
|
||||||
let _span = info_span!("xr_poll_events");
|
let _span = info_span!("xr_poll_events");
|
||||||
let mut buffer = Default::default();
|
let mut buffer = Default::default();
|
||||||
while let Some(event) = instance
|
while let Some(event) = instance
|
||||||
@@ -525,7 +528,7 @@ pub fn poll_events(instance: Res<OxrInstance>, status: Res<XrSharedStatus>) {
|
|||||||
SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting,
|
SessionState::EXITING | SessionState::LOSS_PENDING => XrStatus::Exiting,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
changed_event.send(XrStatusChanged(new_status));
|
||||||
status.set(new_status);
|
status.set(new_status);
|
||||||
}
|
}
|
||||||
InstanceLossPending(_) => {}
|
InstanceLossPending(_) => {}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ pub mod layer_builder;
|
|||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod action_binding;
|
||||||
|
pub mod action_set_attaching;
|
||||||
|
|
||||||
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
||||||
plugins
|
plugins
|
||||||
@@ -44,6 +46,8 @@ pub fn add_xr_plugins<G: PluginGroup>(plugins: G) -> PluginGroupBuilder {
|
|||||||
.add(OxrRenderPlugin)
|
.add(OxrRenderPlugin)
|
||||||
.add(OxrPassthroughPlugin)
|
.add(OxrPassthroughPlugin)
|
||||||
.add(XrCameraPlugin)
|
.add(XrCameraPlugin)
|
||||||
|
.add(action_set_attaching::OxrActionAttachingPlugin)
|
||||||
|
.add(action_binding::OxrActionBindingPlugin)
|
||||||
// .add(XrActionPlugin)
|
// .add(XrActionPlugin)
|
||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ impl Plugin for XrSessionPlugin {
|
|||||||
.add_event::<DestroyXrSession>()
|
.add_event::<DestroyXrSession>()
|
||||||
.add_event::<BeginXrSession>()
|
.add_event::<BeginXrSession>()
|
||||||
.add_event::<EndXrSession>()
|
.add_event::<EndXrSession>()
|
||||||
|
.add_event::<XrStatusChanged>()
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
handle_session.run_if(resource_exists::<XrSharedStatus>),
|
handle_session.run_if(resource_exists::<XrSharedStatus>),
|
||||||
@@ -17,6 +18,9 @@ impl Plugin for XrSessionPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Event, Clone, Copy, Deref)]
|
||||||
|
pub struct XrStatusChanged(pub XrStatus);
|
||||||
|
|
||||||
#[derive(Resource, Clone)]
|
#[derive(Resource, Clone)]
|
||||||
pub struct XrSharedStatus(Arc<RwLock<XrStatus>>);
|
pub struct XrSharedStatus(Arc<RwLock<XrStatus>>);
|
||||||
|
|
||||||
@@ -84,6 +88,13 @@ pub fn handle_session(
|
|||||||
*previous_status = Some(current_status);
|
*previous_status = Some(current_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`Condition`](bevy::ecs::schedule::Condition) that allows the system to run when the xr status changed to a specific [`XrStatus`].
|
||||||
|
pub fn status_changed_to(status: XrStatus) -> impl FnMut(EventReader<XrStatusChanged>) -> bool + Clone {
|
||||||
|
move |mut reader: EventReader<XrStatusChanged>| {
|
||||||
|
reader.read().any(|new_status| new_status.0 == status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrStatus`] exists and isn't [`Unavailable`](XrStatus::Unavailable).
|
/// A [`Condition`](bevy::ecs::schedule::Condition) system that says if the XR session is available. Returns true as long as [`XrStatus`] exists and isn't [`Unavailable`](XrStatus::Unavailable).
|
||||||
pub fn session_available(status: Option<Res<XrSharedStatus>>) -> bool {
|
pub fn session_available(status: Option<Res<XrSharedStatus>>) -> bool {
|
||||||
status.is_some_and(|s| s.get() != XrStatus::Unavailable)
|
status.is_some_and(|s| s.get() != XrStatus::Unavailable)
|
||||||
|
|||||||
Reference in New Issue
Block a user