move to api crate

This commit is contained in:
awtterpip
2023-12-26 14:20:22 -06:00
parent 037f719329
commit ffa9e6d080
26 changed files with 565 additions and 1310 deletions

69
xr_api/Cargo.toml Normal file
View File

@@ -0,0 +1,69 @@
[package]
name = "xr_api"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
futures = "0.3.29"
thiserror = "1.0.51"
[target.'cfg(not(target_family = "wasm"))'.dependencies]
openxr = "0.17.1"
[target.'cfg(target_family = "wasm")'.dependencies]
wasm-bindgen = "0.2.87"
web-sys = { version = "0.3.61", features = [
# STANDARD
'console',
'Document',
'Element',
'Headers',
'Navigator',
'Window',
# IO
# 'Url',
# WEBGL
'Gpu',
'HtmlCanvasElement',
'WebGl2RenderingContext',
'WebGlFramebuffer',
## XR
'DomPointReadOnly',
'XrWebGlLayer',
'XrBoundedReferenceSpace',
'XrEye',
'XrFrame',
'XrHandedness',
'XrInputSource',
'XrInputSourceArray',
'XrInputSourceEvent',
'XrInputSourceEventInit',
'XrInputSourcesChangeEvent',
'XrJointPose',
'XrJointSpace',
'XrPose',
'XrReferenceSpace',
'XrReferenceSpaceEvent',
'XrReferenceSpaceEventInit',
'XrReferenceSpaceType',
'XrRenderState',
'XrRenderStateInit',
'XrRigidTransform',
'XrSession',
'XrSessionEvent',
'XrSessionEventInit',
'XrSessionInit',
'XrSessionMode',
'XrSpace',
'XrTargetRayMode',
'XrView',
'XrViewerPose',
'XrViewport',
'XrVisibilityState',
'XrWebGlLayer',
'XrWebGlLayerInit',
'XrSystem',
] }
wasm-bindgen-futures = "0.4"

111
xr_api/src/api.rs Normal file
View File

@@ -0,0 +1,111 @@
use std::ops::Deref;
use std::rc::Rc;
use crate::prelude::*;
#[derive(Clone)]
pub struct Entry(Rc<dyn EntryTrait>);
#[derive(Clone)]
pub struct Instance(Rc<dyn InstanceTrait>);
#[derive(Clone)]
pub struct Session(Rc<dyn SessionTrait>);
#[derive(Clone)]
pub struct Input(Rc<dyn InputTrait>);
#[derive(Clone)]
pub struct Action<A: ActionType>(A::Inner);
impl Deref for Entry {
type Target = dyn EntryTrait;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl Deref for Instance {
type Target = dyn InstanceTrait;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl Deref for Session {
type Target = dyn SessionTrait;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl Deref for Input {
type Target = dyn InputTrait;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<O, A> Deref for Action<A>
where
A: ActionType,
A::Inner: Deref<Target = O>,
{
type Target = O;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl<T: EntryTrait + 'static> From<T> for Entry {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: InstanceTrait + 'static> From<T> for Instance {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: SessionTrait + 'static> From<T> for Session {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: InputTrait + 'static> From<T> for Input {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: HapticTrait + 'static> From<T> for Action<Haptic> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<f32> + 'static> From<T> for Action<f32> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<Pose> + 'static> From<T> for Action<Pose> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}
impl<T: ActionInputTrait<bool> + 'static> From<T> for Action<bool> {
fn from(value: T) -> Self {
Self(Rc::new(value))
}
}

67
xr_api/src/api_traits.rs Normal file
View File

@@ -0,0 +1,67 @@
use crate::prelude::*;
pub trait EntryTrait {
/// Return currently available extensions
fn available_extensions(&self) -> Result<ExtensionSet>;
/// Create an [Instance] with the enabled extensions.
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance>;
}
pub trait InstanceTrait {
/// Returns the [Entry] used to create this.
fn entry(&self) -> Entry;
/// Returns an [ExtensionSet] listing all enabled extensions.
fn enabled_extensions(&self) -> ExtensionSet;
/// Creates a [Session] with the requested properties
fn create_session(&self, info: SessionCreateInfo) -> Result<Session>;
}
pub trait SessionTrait {
/// Returns the [Instance] used to create this.
fn instance(&self) -> &Instance;
/// Request input modules with the specified bindings.
fn create_input(&self, bindings: Bindings) -> Result<Input>;
/// Blocks until a rendering frame is available and then begins it.
fn begin_frame(&self) -> Result<()>;
/// Submits rendering work for this frame.
fn end_frame(&self) -> Result<()>;
}
pub trait InputTrait {
fn get_haptics(&self, path: ActionId) -> Result<Action<Haptic>>;
fn get_pose(&self, path: ActionId) -> Result<Action<Pose>>;
fn get_float(&self, path: ActionId) -> Result<Action<f32>>;
fn get_bool(&self, path: ActionId) -> Result<Action<bool>>;
}
pub trait ActionTrait {
fn id(&self) -> ActionId;
}
pub trait ActionInputTrait<A> {}
pub trait HapticTrait {}
impl<T: InstanceTrait> EntryTrait for T {
fn available_extensions(&self) -> Result<ExtensionSet> {
self.entry().available_extensions()
}
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
self.entry().create_instance(exts)
}
}
impl<T: SessionTrait> InstanceTrait for T {
fn entry(&self) -> Entry {
self.instance().entry()
}
fn enabled_extensions(&self) -> ExtensionSet {
self.instance().enabled_extensions()
}
fn create_session(&self, info: SessionCreateInfo) -> Result<Session> {
self.instance().create_session(info)
}
}

View File

@@ -0,0 +1,4 @@
#[cfg(not(target_family = "wasm"))]
pub mod oxr;
#[cfg(target_family = "wasm")]
pub mod webxr;

30
xr_api/src/backend/oxr.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::prelude::*;
pub struct OXrEntry(openxr::Entry);
impl EntryTrait for OXrEntry {
fn available_extensions(&self) -> Result<ExtensionSet> {
// self.0.enumerate_extensions();
Ok(ExtensionSet::default())
}
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
todo!()
}
}
pub struct OXrInstance(openxr::Instance);
impl InstanceTrait for OXrInstance {
fn entry(&self) -> Entry {
OXrEntry(self.0.entry().clone()).into()
}
fn enabled_extensions(&self) -> ExtensionSet {
todo!()
}
fn create_session(&self, info: SessionCreateInfo) -> Result<Session> {
todo!()
}
}

View File

181
xr_api/src/backend/webxr.rs Normal file
View File

@@ -0,0 +1,181 @@
use std::sync::{
mpsc::{channel, Sender},
Mutex,
};
use crate::prelude::*;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{js_sys, XrFrame, XrInputSource};
mod utils;
use utils::*;
#[derive(Clone)]
pub struct WebXrEntry(web_sys::XrSystem);
impl EntryTrait for WebXrEntry {
fn available_extensions(&self) -> Result<ExtensionSet> {
Ok(ExtensionSet::default())
}
fn create_instance(&self, exts: ExtensionSet) -> Result<Instance> {
Ok(WebXrInstance {
entry: self.clone(),
exts,
}
.into())
}
}
#[derive(Clone)]
pub struct WebXrInstance {
entry: WebXrEntry,
exts: ExtensionSet,
}
impl InstanceTrait for WebXrInstance {
fn entry(&self) -> Entry {
self.entry.clone().into()
}
fn enabled_extensions(&self) -> ExtensionSet {
self.exts
}
fn create_session(&self, info: SessionCreateInfo) -> Result<Session> {
Ok(WebXrSession {
instance: self.clone().into(),
session: self
.entry
.0
.request_session(web_sys::XrSessionMode::ImmersiveVr)
.resolve()
.map_err(|_| XrError::Placeholder)?,
end_frame_sender: Mutex::default(),
}
.into())
}
}
pub struct WebXrSession {
instance: Instance,
session: web_sys::XrSession,
end_frame_sender: Mutex<Option<Sender<()>>>,
}
impl SessionTrait for WebXrSession {
fn instance(&self) -> &Instance {
&self.instance
}
fn create_input(&self, bindings: Bindings) -> Result<Input> {
Ok(WebXrInput {
devices: self.session.input_sources(),
bindings,
}
.into())
}
fn begin_frame(&self) -> Result<()> {
let mut end_frame_sender = self.end_frame_sender.lock().unwrap();
if end_frame_sender.is_some() {
Err(XrError::Placeholder)?
}
let (tx, rx) = channel::<()>();
let (tx_end, rx_end) = channel::<()>();
*end_frame_sender = Some(tx_end);
let on_frame: Closure<dyn FnMut(f64, XrFrame)> =
Closure::new(move |time: f64, frame: XrFrame| {
tx.send(()).ok();
rx_end.recv().ok();
});
self.session
.request_animation_frame(on_frame.as_ref().unchecked_ref());
rx.recv().ok();
Ok(())
}
fn end_frame(&self) -> Result<()> {
let mut end_frame_sender = self.end_frame_sender.lock().unwrap();
match std::mem::take(&mut *end_frame_sender) {
Some(sender) => sender.send(()).ok(),
None => Err(XrError::Placeholder)?,
};
Ok(())
}
}
pub struct WebXrInput {
devices: web_sys::XrInputSourceArray,
bindings: Bindings,
}
impl From<web_sys::XrHandedness> for Handedness {
fn from(value: web_sys::XrHandedness) -> Self {
match value {
web_sys::XrHandedness::None => Handedness::None,
web_sys::XrHandedness::Left => Handedness::Left,
web_sys::XrHandedness::Right => Handedness::Right,
_ => todo!(),
}
}
}
impl WebXrInput {
fn get_controller(&self, handedness: Handedness) -> Option<web_sys::XrInputSource> {
js_sys::try_iter(&self.devices).ok()??.find_map(|dev| {
if let Ok(dev) = dev {
let dev: XrInputSource = dev.into();
if Into::<Handedness>::into(dev.handedness()) == handedness {
Some(dev)
} else {
None
}
} else {
None
}
})
}
}
impl InputTrait for WebXrInput {
fn get_haptics(&self, path: ActionId) -> Result<Action<Haptic>> {
let haptics = self
.get_controller(path.handedness)
.ok_or(XrError::Placeholder)?
.gamepad()
.ok_or(XrError::Placeholder)?
.haptic_actuators()
.iter()
.next()
.ok_or(XrError::Placeholder)?
.into();
Ok(WebXrHaptics(haptics, path).into())
}
fn get_pose(&self, path: ActionId) -> Result<Action<Pose>> {
todo!()
}
fn get_float(&self, path: ActionId) -> Result<Action<f32>> {
todo!()
}
fn get_bool(&self, path: ActionId) -> Result<Action<bool>> {
todo!()
}
}
pub struct WebXrHaptics(web_sys::GamepadHapticActuator, ActionId);
impl ActionTrait for WebXrHaptics {
fn id(&self) -> ActionId {
self.1
}
}
impl HapticTrait for WebXrHaptics {}

View File

@@ -0,0 +1,17 @@
use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture;
use web_sys::js_sys::Promise;
pub trait PromiseRes {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue>;
}
impl PromiseRes for Promise {
fn resolve<T: From<JsValue>>(self) -> Result<T, JsValue> {
resolve_promise(self)
}
}
pub fn resolve_promise<T: From<JsValue>>(promise: Promise) -> Result<T, JsValue> {
futures::executor::block_on(async move { JsFuture::from(promise).await.map(Into::into) })
}

16
xr_api/src/error.rs Normal file
View File

@@ -0,0 +1,16 @@
use std::fmt::Display;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, XrError>;
#[derive(Error, Debug)]
pub enum XrError {
Placeholder,
}
impl Display for XrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

12
xr_api/src/lib.rs Normal file
View File

@@ -0,0 +1,12 @@
pub mod api;
pub mod api_traits;
pub mod backend;
pub mod error;
pub mod types;
pub mod prelude {
pub use super::api::*;
pub use super::api_traits::*;
pub use super::error::*;
pub use super::types::*;
}

51
xr_api/src/types.rs Normal file
View File

@@ -0,0 +1,51 @@
use std::rc::Rc;
use crate::api_traits::{ActionInputTrait, HapticTrait};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ExtensionSet {}
pub enum SessionCreateInfo {}
pub struct Bindings {}
#[derive(Clone, Copy, PartialEq)]
pub struct ActionId {
pub handedness: Handedness,
pub device: Device,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Handedness {
Left,
Right,
None,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Device {
Controller,
}
pub struct Haptic;
pub struct Pose;
pub trait ActionType {
type Inner;
}
impl ActionType for Haptic {
type Inner = Rc<dyn HapticTrait>;
}
impl ActionType for Pose {
type Inner = Rc<dyn ActionInputTrait<Pose>>;
}
impl ActionType for f32 {
type Inner = Rc<dyn ActionInputTrait<f32>>;
}
impl ActionType for bool {
type Inner = Rc<dyn ActionInputTrait<bool>>;
}