bevy_xr_utils/
actions.rs

1//! This plugin and module are here to ease the creation of actions withing openxr
2//! The general idea is any plugin can create entities in startup before XRUtilsActionSystemSet::CreateEvents
3//! this plugin will then create the neccessary actions sets, actions, and bindings and get them ready for use.
4//!
5//! example creating actions
6//!
7//!          //create a set
8//!     let set = commands
9//!     .spawn((
10//!         XRUtilsActionSet {
11//!             name: "flight".into(),
12//!             pretty_name: "pretty flight set".into(),
13//!             priority: u32::MIN,
14//!         },
15//!         ActiveSet, //marker to indicate we want this synced
16//!     ))
17//!     .id();
18//!     //create an action
19//!     let action = commands
20//!     .spawn((
21//!         XRUtilsAction {
22//!             action_name: "flight_input".into(),
23//!             localized_name: "flight_input_localized".into(),
24//!             action_type: bevy_mod_xr::actions::ActionType::Vector,
25//!         },
26//!         FlightActionMarker, //lets try a marker component
27//!     ))
28//!     .id();
29//!     
30//!     //create a binding
31//!     let binding = commands
32//!     .spawn(XRUtilsBinding {
33//!         profile: "/interaction_profiles/valve/index_controller".into(),
34//!         binding: "/user/hand/right/input/thumbstick".into(),
35//!     })
36//!     .id();
37//!     
38//!     //add action to set, this isnt the best
39//!     //TODO look into a better system
40//!     commands.entity(action).add_child(binding);
41//!     commands.entity(set).add_child(action);
42//!
43//! then you can read the action states after XRUtilsActionSystemSet::SyncActionStates
44//! for example
45//!
46//! fn read_action_with_marker_component(
47//!     mut action_query: Query<&XRUtilsActionState, With<FlightActionMarker>>,
48//!     ) {
49//!         //now for the actual checking
50//!         for state in action_query.iter_mut() {
51//!             info!("action state is: {:?}", state);
52//!         }
53//!     }
54//!
55//!
56use bevy_app::{App, Plugin, PreUpdate, Startup, Update};
57use bevy_ecs::{component::Component, entity::Entity, hierarchy::Children, message::MessageWriter, query::With, schedule::{IntoScheduleConfigs as _, SystemSet}, system::{Commands, Query, Res, ResMut}};
58use bevy_log::info;
59use bevy_mod_openxr::{
60    action_binding::OxrSuggestActionBinding,
61    action_set_attaching::OxrAttachActionSet,
62    action_set_syncing::{OxrActionSetSyncSet, OxrSyncActionSet},
63    openxr_session_available, openxr_session_running,
64    resources::OxrInstance,
65    session::OxrSession,
66};
67use openxr::{Path, Vector2f};
68
69use std::borrow::Cow;
70
71pub struct XRUtilsActionsPlugin;
72impl Plugin for XRUtilsActionsPlugin {
73    fn build(&self, app: &mut App) {
74        app.configure_sets(
75            Startup,
76            XRUtilsActionSystems::CreateEvents.run_if(openxr_session_available),
77        );
78        app.configure_sets(
79            PreUpdate,
80            XRUtilsActionSystems::SyncActionStates.run_if(openxr_session_running),
81        );
82        app.add_systems(
83            Startup,
84            create_openxr_events
85                .in_set(XRUtilsActionSystems::CreateEvents)
86                .run_if(openxr_session_available),
87        );
88        app.add_systems(
89            Update,
90            sync_active_action_sets.run_if(openxr_session_running),
91        );
92        app.add_systems(
93            PreUpdate,
94            sync_and_update_action_states_f32
95                .run_if(openxr_session_running)
96                .in_set(XRUtilsActionSystems::SyncActionStates)
97                .after(OxrActionSetSyncSet),
98        );
99        app.add_systems(
100            PreUpdate,
101            sync_and_update_action_states_bool
102                .run_if(openxr_session_running)
103                .in_set(XRUtilsActionSystems::SyncActionStates)
104                .after(OxrActionSetSyncSet),
105        );
106        app.add_systems(
107            PreUpdate,
108            sync_and_update_action_states_vector
109                .run_if(openxr_session_running)
110                .in_set(XRUtilsActionSystems::SyncActionStates)
111                .after(OxrActionSetSyncSet),
112        );
113    }
114}
115
116fn create_openxr_events(
117    action_sets_query: Query<(&XRUtilsActionSet, &Children, Entity)>,
118    actions_query: Query<(&XRUtilsAction, &Children)>,
119    bindings_query: Query<&XRUtilsBinding>,
120    instance: ResMut<OxrInstance>,
121    mut binding_writer: MessageWriter<OxrSuggestActionBinding>,
122    mut attach_writer: MessageWriter<OxrAttachActionSet>,
123    mut commands: Commands,
124) {
125    //lets create some sets!
126    for (set, children, id) in action_sets_query.iter() {
127        //create action set
128        let action_set: openxr::ActionSet = instance
129            .create_action_set(&set.name, &set.pretty_name, set.priority)
130            .unwrap();
131        //now that we have the action set we need to put it back onto the entity for later
132        let oxr_action_set = XRUtilsActionSetReference(action_set.clone());
133        commands.entity(id).insert(oxr_action_set);
134
135        //since the actions are made from the sets lets go
136        for child in children.iter().copied() {
137            //first get the action entity and stuff
138            let (create_action, bindings) = actions_query.get(child).unwrap();
139            //lets create dat action
140            match create_action.action_type {
141                ActionType::Bool => {
142                    let action: openxr::Action<bool> = action_set
143                        .create_action::<bool>(
144                            &create_action.action_name,
145                            &create_action.localized_name,
146                            &[],
147                        )
148                        .unwrap();
149                    //please put this in a function so I dont go crazy
150                    //insert a reference for later
151                    commands.entity(child).insert((
152                        ActionBooleference {
153                            action: action.clone(),
154                        },
155                        XRUtilsActionState::Bool(ActionStateBool {
156                            current_state: false,
157                            changed_since_last_sync: false,
158                            last_change_time: i64::MIN,
159                            is_active: false,
160                        }),
161                    ));
162                    //since we need actions for bindings lets go!!
163                    for bind in bindings.iter().copied() {
164                        //interaction profile
165                        //get the binding entity and stuff
166                        let create_binding = bindings_query.get(bind).unwrap();
167                        let profile = create_binding.profile.clone();
168                        //bindings
169                        let binding = vec![create_binding.binding.clone()];
170                        let sugestion = OxrSuggestActionBinding {
171                            action: action.as_raw(),
172                            interaction_profile: profile,
173                            bindings: binding,
174                        };
175                        //finally send the suggestion
176                        binding_writer.write(sugestion);
177                    }
178                }
179                ActionType::Float => {
180                    let action: openxr::Action<f32> = action_set
181                        .create_action::<f32>(
182                            &create_action.action_name,
183                            &create_action.localized_name,
184                            &[],
185                        )
186                        .unwrap();
187
188                    //please put this in a function so I dont go crazy
189                    //insert a reference for later
190                    commands.entity(child).insert((
191                        Actionf32Reference {
192                            action: action.clone(),
193                        },
194                        XRUtilsActionState::Float(ActionStateFloat {
195                            current_state: 0.0,
196                            changed_since_last_sync: false,
197                            last_change_time: i64::MIN,
198                            is_active: false,
199                        }),
200                    ));
201                    //since we need actions for bindings lets go!!
202                    for bind in bindings.iter().copied() {
203                        //interaction profile
204                        //get the binding entity and stuff
205                        let create_binding = bindings_query.get(bind).unwrap();
206                        let profile = create_binding.profile.clone();
207                        //bindings
208                        let binding = vec![create_binding.binding.clone()];
209                        let sugestion = OxrSuggestActionBinding {
210                            action: action.as_raw(),
211                            interaction_profile: profile,
212                            bindings: binding,
213                        };
214                        //finally send the suggestion
215                        binding_writer.write(sugestion);
216                    }
217                }
218                ActionType::Vector => {
219                    let action: openxr::Action<Vector2f> = action_set
220                        .create_action::<Vector2f>(
221                            &create_action.action_name,
222                            &create_action.localized_name,
223                            &[],
224                        )
225                        .unwrap();
226
227                    //please put this in a function so I dont go crazy
228                    //insert a reference for later
229                    commands.entity(child).insert((
230                        ActionVector2fReference {
231                            action: action.clone(),
232                        },
233                        XRUtilsActionState::Vector(ActionStateVector {
234                            current_state: [0.0, 0.0],
235                            changed_since_last_sync: false,
236                            last_change_time: i64::MIN,
237                            is_active: false,
238                        }),
239                    ));
240                    //since we need actions for bindings lets go!!
241                    for bind in bindings.iter().copied() {
242                        //interaction profile
243                        //get the binding entity and stuff
244                        let create_binding = bindings_query.get(bind).unwrap();
245                        let profile = create_binding.profile.clone();
246                        //bindings
247                        let binding = vec![create_binding.binding.clone()];
248                        let sugestion = OxrSuggestActionBinding {
249                            action: action.as_raw(),
250                            interaction_profile: profile,
251                            bindings: binding,
252                        };
253                        //finally send the suggestion
254                        binding_writer.write(sugestion);
255                    }
256                }
257            };
258        }
259
260        attach_writer.write(OxrAttachActionSet(action_set));
261    }
262}
263
264fn sync_active_action_sets(
265    mut sync_set: MessageWriter<OxrSyncActionSet>,
266    active_action_set_query: Query<&XRUtilsActionSetReference, With<ActiveSet>>,
267) {
268    for set in &active_action_set_query {
269        sync_set.write(OxrSyncActionSet(set.0.clone()));
270    }
271}
272
273fn sync_and_update_action_states_f32(
274    session: Res<OxrSession>,
275    mut f32_query: Query<(&Actionf32Reference, &mut XRUtilsActionState)>,
276) {
277    //now we do the action state for f32
278    for (reference, mut silly_state) in f32_query.iter_mut() {
279        let state = reference.action.state(&session, Path::NULL);
280        match state {
281            Ok(s) => {
282                let new_state = XRUtilsActionState::Float(ActionStateFloat {
283                    current_state: s.current_state,
284                    changed_since_last_sync: s.changed_since_last_sync,
285                    last_change_time: s.last_change_time.as_nanos(),
286                    is_active: s.is_active,
287                });
288
289                *silly_state = new_state;
290            }
291            Err(_) => {
292                info!("error getting action state");
293            }
294        }
295    }
296}
297
298fn sync_and_update_action_states_bool(
299    session: Res<OxrSession>,
300    mut f32_query: Query<(&ActionBooleference, &mut XRUtilsActionState)>,
301) {
302    //now we do the action state for f32
303    for (reference, mut silly_state) in f32_query.iter_mut() {
304        let state = reference.action.state(&session, Path::NULL);
305        match state {
306            Ok(s) => {
307                let new_state = XRUtilsActionState::Bool(ActionStateBool {
308                    current_state: s.current_state,
309                    changed_since_last_sync: s.changed_since_last_sync,
310                    last_change_time: s.last_change_time.as_nanos(),
311                    is_active: s.is_active,
312                });
313
314                *silly_state = new_state;
315            }
316            Err(_) => {
317                info!("error getting action state");
318            }
319        }
320    }
321}
322
323fn sync_and_update_action_states_vector(
324    session: Res<OxrSession>,
325    mut vector_query: Query<(&ActionVector2fReference, &mut XRUtilsActionState)>,
326) {
327    //now we do the action state for f32
328    for (reference, mut silly_state) in vector_query.iter_mut() {
329        let state = reference.action.state(&session, Path::NULL);
330        match state {
331            Ok(s) => {
332                let new_state = XRUtilsActionState::Vector(ActionStateVector {
333                    current_state: [s.current_state.x, s.current_state.y],
334                    changed_since_last_sync: s.changed_since_last_sync,
335                    last_change_time: s.last_change_time.as_nanos(),
336                    is_active: s.is_active,
337                });
338
339                *silly_state = new_state;
340            }
341            Err(_) => {
342                info!("error getting action state");
343            }
344        }
345    }
346}
347
348#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
349pub enum ActionType {
350    Bool,
351    Float,
352    Vector,
353}
354
355#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
356pub enum XRUtilsActionSystems {
357    /// Runs in Startup
358    CreateEvents,
359    /// Runs in PreUpdate
360    SyncActionStates,
361}
362
363#[derive(Component)]
364pub struct XRUtilsActionSet {
365    pub name: Cow<'static, str>,
366    pub pretty_name: Cow<'static, str>,
367    pub priority: u32,
368}
369
370#[derive(Component, Clone)]
371pub struct XRUtilsActionSetReference(pub openxr::ActionSet);
372
373//I want to use this to indicate when an action set is attached
374// #[derive(Component)]
375// struct AttachedActionSet;
376
377//this is used to determine if this set should be synced
378#[derive(Component)]
379pub struct ActiveSet;
380
381#[derive(Component)]
382pub struct XRUtilsAction {
383    pub action_name: Cow<'static, str>,
384    pub localized_name: Cow<'static, str>,
385    pub action_type: ActionType,
386}
387
388#[derive(Component)]
389pub struct XRUtilsBinding {
390    pub profile: Cow<'static, str>,
391    pub binding: Cow<'static, str>,
392}
393
394//Prototype action states
395//TODO refactor this
396#[derive(Component, Debug)]
397pub enum XRUtilsActionState {
398    Bool(ActionStateBool),
399    Float(ActionStateFloat),
400    Vector(ActionStateVector),
401}
402
403#[derive(Debug)]
404pub struct ActionStateBool {
405    pub current_state: bool,
406    pub changed_since_last_sync: bool,
407    pub last_change_time: i64,
408    pub is_active: bool,
409}
410#[derive(Debug)]
411pub struct ActionStateFloat {
412    pub current_state: f32,
413    pub changed_since_last_sync: bool,
414    pub last_change_time: i64,
415    pub is_active: bool,
416}
417#[derive(Debug)]
418pub struct ActionStateVector {
419    pub current_state: [f32; 2],
420    pub changed_since_last_sync: bool,
421    pub last_change_time: i64,
422    pub is_active: bool,
423}
424
425//prototype action references
426//TODO refactor along with action states
427#[derive(Component)]
428struct Actionf32Reference {
429    action: openxr::Action<f32>,
430}
431
432#[derive(Component)]
433struct ActionBooleference {
434    action: openxr::Action<bool>,
435}
436
437#[derive(Component)]
438struct ActionVector2fReference {
439    action: openxr::Action<Vector2f>,
440}