Skip to main content

openxr_session/
openxr_session.rs

1//! `OpenXR` session lifecycle.
2//!
3//! The runtime drives the session through modes.
4//! The machine never transitions itself.
5
6#![allow(clippy::print_stdout, clippy::enum_glob_use)]
7
8use ready_active_safe::prelude::*;
9
10/// XR session lifecycle modes.
11///
12/// These map directly to the `OpenXR` session states, but modeled as
13/// lifecycle phases rather than an arbitrary state graph.
14#[derive(Debug, Clone, PartialEq, Eq)]
15enum SessionMode {
16    /// Session created but not yet ready to render.
17    Idle,
18    /// Runtime is ready. The application should prepare resources.
19    Ready,
20    /// Frames are being composed but not yet visible.
21    Synchronized,
22    /// Session is visible to the user.
23    Visible,
24    /// Session has input focus.
25    Focused,
26    /// Session is stopping. Release resources.
27    Stopping,
28}
29
30impl core::fmt::Display for SessionMode {
31    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32        write!(f, "{self:?}")
33    }
34}
35
36/// Events from the XR runtime.
37#[derive(Debug)]
38enum XrEvent {
39    /// The runtime signals a session state change.
40    SessionStateChanged(SessionMode),
41    /// A frame has been waited on and is available.
42    FrameReady,
43    /// The user or runtime requests session end.
44    RequestExit,
45}
46
47/// Commands the application should execute in response.
48#[derive(Debug, PartialEq)]
49enum XrCommand {
50    /// Load shaders, meshes, and textures.
51    PrepareResources,
52    /// Begin the frame submission loop.
53    BeginFrameLoop,
54    /// Submit a frame to the compositor.
55    SubmitFrame,
56    /// Acquire input device state.
57    PollInput,
58    /// Release GPU resources and end the session.
59    ReleaseResources,
60    /// Call `xrEndSession`.
61    EndSession,
62}
63
64struct XrSession;
65
66impl Machine for XrSession {
67    type Mode = SessionMode;
68    type Event = XrEvent;
69    type Command = XrCommand;
70
71    fn initial_mode(&self) -> SessionMode {
72        SessionMode::Idle
73    }
74
75    fn on_event(
76        &self,
77        mode: &Self::Mode,
78        event: &Self::Event,
79    ) -> Decision<Self::Mode, Self::Command> {
80        use SessionMode::*;
81        use XrCommand::*;
82        use XrEvent::*;
83
84        match (mode, event) {
85            (Idle, SessionStateChanged(Ready)) => transition(Ready).emit(PrepareResources),
86            (Ready, SessionStateChanged(Synchronized)) => {
87                transition(Synchronized).emit(BeginFrameLoop)
88            }
89            (Synchronized, SessionStateChanged(Visible)) => transition(Visible),
90            (Visible, SessionStateChanged(Focused)) => transition(Focused).emit(PollInput),
91            (Focused, FrameReady) => stay().emit_all([SubmitFrame, PollInput]),
92            (Visible, FrameReady) => stay().emit(SubmitFrame),
93            (mode, RequestExit) if *mode != Idle && *mode != Stopping => {
94                transition(Stopping).emit_all([ReleaseResources, EndSession])
95            }
96            _ => ignore(),
97        }
98    }
99}
100
101fn main() {
102    let session = XrSession;
103    let mut mode = session.initial_mode();
104
105    let events = [
106        XrEvent::SessionStateChanged(SessionMode::Ready),
107        XrEvent::SessionStateChanged(SessionMode::Synchronized),
108        XrEvent::SessionStateChanged(SessionMode::Visible),
109        XrEvent::SessionStateChanged(SessionMode::Focused),
110        XrEvent::FrameReady,
111        XrEvent::FrameReady,
112        XrEvent::RequestExit,
113    ];
114
115    for event in &events {
116        let decision = session.decide(&mode, event);
117        println!("{mode:?} + {event:?} => {decision}");
118
119        let (next_mode, commands) = decision.apply(mode);
120        if !commands.is_empty() {
121            println!("  commands: {commands:?}");
122        }
123
124        mode = next_mode;
125    }
126
127    assert_eq!(mode, SessionMode::Stopping);
128}