servo-webxr-api 0.1.0

A safe Rust API that provides a way to interact with virtual reality and augmented reality devices and integration with OpenXR. The API is inspired by the WebXR Device API (https://www.w3.org/TR/webxr/) but adapted to Rust design patterns.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use embedder_traits::EventLoopWaker;
use ipc_channel::ipc::IpcSender;
use log::warn;
use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
use serde::{Deserialize, Serialize};
use servo_base::generic_channel::{self, GenericCallback, GenericReceiver, GenericSender};

use crate::{
    DiscoveryAPI, Error, Frame, GLTypes, LayerGrandManager, MainThreadSession, MockDeviceInit,
    MockDeviceMsg, MockDiscoveryAPI, Session, SessionBuilder, SessionId, SessionInit, SessionMode,
};

#[derive(Clone, Serialize, Deserialize)]
pub struct Registry {
    sender: GenericSender<RegistryMsg>,
    waker: MainThreadWakerImpl,
}

pub struct MainThreadRegistry<GL> {
    discoveries: Vec<Box<dyn DiscoveryAPI<GL>>>,
    sessions: Vec<Box<dyn MainThreadSession>>,
    mocks: Vec<Box<dyn MockDiscoveryAPI<GL>>>,
    sender: GenericSender<RegistryMsg>,
    receiver: GenericReceiver<RegistryMsg>,
    waker: MainThreadWakerImpl,
    grand_manager: LayerGrandManager<GL>,
    next_session_id: u32,
}

#[derive(Clone, Serialize, Deserialize)]
struct MainThreadWakerImpl {
    callback: GenericCallback<()>,
}

impl MainThreadWakerImpl {
    fn new(waker: Box<dyn EventLoopWaker>) -> Result<MainThreadWakerImpl, Error> {
        let callback =
            GenericCallback::new(move |_| waker.wake()).expect("Could not construct callback");
        Ok(MainThreadWakerImpl { callback })
    }

    fn wake(&self) {
        let _ = self.callback.send(());
    }
}

impl Registry {
    pub fn supports_session(
        &mut self,
        mode: SessionMode,
        dest: ProfileGenericCallback<Result<(), Error>>,
    ) {
        let _ = self.sender.send(RegistryMsg::SupportsSession(mode, dest));
        self.waker.wake();
    }

    pub fn request_session(
        &mut self,
        mode: SessionMode,
        init: SessionInit,
        dest: IpcSender<Result<Session, Error>>,
        animation_frame_handler: IpcSender<Frame>,
    ) {
        let _ = self.sender.send(RegistryMsg::RequestSession(
            mode,
            init,
            dest,
            animation_frame_handler,
        ));
        self.waker.wake();
    }

    pub fn simulate_device_connection(
        &mut self,
        init: MockDeviceInit,
        dest: ProfileGenericCallback<Result<GenericSender<MockDeviceMsg>, Error>>,
    ) {
        let _ = self
            .sender
            .send(RegistryMsg::SimulateDeviceConnection(init, dest));
        self.waker.wake();
    }
}

impl<GL: 'static + GLTypes> MainThreadRegistry<GL> {
    pub fn new(
        waker: Box<dyn EventLoopWaker>,
        grand_manager: LayerGrandManager<GL>,
    ) -> Result<Self, Error> {
        let Some((sender, receiver)) = generic_channel::channel() else {
            return Err(Error::CommunicationError);
        };
        let discoveries = Vec::new();
        let sessions = Vec::new();
        let mocks = Vec::new();
        let waker = MainThreadWakerImpl::new(waker)?;
        Ok(MainThreadRegistry {
            discoveries,
            sessions,
            mocks,
            sender,
            receiver,
            waker,
            grand_manager,
            next_session_id: 0,
        })
    }

    pub fn registry(&self) -> Registry {
        Registry {
            sender: self.sender.clone(),
            waker: self.waker.clone(),
        }
    }

    pub fn register<D>(&mut self, discovery: D)
    where
        D: DiscoveryAPI<GL>,
    {
        self.discoveries.push(Box::new(discovery));
    }

    pub fn register_mock<D>(&mut self, discovery: D)
    where
        D: MockDiscoveryAPI<GL>,
    {
        self.mocks.push(Box::new(discovery));
    }

    pub fn run_on_main_thread<S>(&mut self, session: S)
    where
        S: MainThreadSession,
    {
        self.sessions.push(Box::new(session));
    }

    pub fn run_one_frame(&mut self) {
        while let Ok(msg) = self.receiver.try_recv() {
            self.handle_msg(msg);
        }
        for session in &mut self.sessions {
            session.run_one_frame();
        }
        self.sessions.retain(|session| session.running());
    }

    pub fn running(&self) -> bool {
        self.sessions.iter().any(|session| session.running())
    }

    fn handle_msg(&mut self, msg: RegistryMsg) {
        match msg {
            RegistryMsg::SupportsSession(mode, dest) => {
                let _ = dest.send(self.supports_session(mode));
            },
            RegistryMsg::RequestSession(mode, init, dest, raf_sender) => {
                let _ = dest.send(self.request_session(mode, init, raf_sender));
            },
            RegistryMsg::SimulateDeviceConnection(init, dest) => {
                let _ = dest.send(self.simulate_device_connection(init));
            },
        }
    }

    fn supports_session(&mut self, mode: SessionMode) -> Result<(), Error> {
        for discovery in &self.discoveries {
            if discovery.supports_session(mode) {
                return Ok(());
            }
        }
        Err(Error::NoMatchingDevice)
    }

    fn request_session(
        &mut self,
        mode: SessionMode,
        init: SessionInit,
        raf_sender: IpcSender<Frame>,
    ) -> Result<Session, Error> {
        for discovery in &mut self.discoveries {
            if discovery.supports_session(mode) {
                let raf_sender = raf_sender.clone();
                let id = SessionId(self.next_session_id);
                self.next_session_id += 1;
                let xr = SessionBuilder::new(
                    &mut self.sessions,
                    raf_sender,
                    self.grand_manager.clone(),
                    id,
                );
                match discovery.request_session(mode, &init, xr) {
                    Ok(session) => return Ok(session),
                    Err(err) => warn!("XR device error {:?}", err),
                }
            }
        }
        warn!("no device could support the session");
        Err(Error::NoMatchingDevice)
    }

    fn simulate_device_connection(
        &mut self,
        init: MockDeviceInit,
    ) -> Result<GenericSender<MockDeviceMsg>, Error> {
        for mock in &mut self.mocks {
            let Some((sender, receiver)) = generic_channel::channel() else {
                return Err(Error::CommunicationError);
            };
            if let Ok(discovery) = mock.simulate_device_connection(init.clone(), receiver) {
                self.discoveries.insert(0, discovery);
                return Ok(sender);
            }
        }
        Err(Error::NoMatchingDevice)
    }
}

#[derive(Serialize, Deserialize)]
#[expect(clippy::large_enum_variant)]
enum RegistryMsg {
    RequestSession(
        SessionMode,
        SessionInit,
        IpcSender<Result<Session, Error>>,
        IpcSender<Frame>,
    ),
    SupportsSession(SessionMode, ProfileGenericCallback<Result<(), Error>>),
    SimulateDeviceConnection(
        MockDeviceInit,
        ProfileGenericCallback<Result<GenericSender<MockDeviceMsg>, Error>>,
    ),
}