Skip to main content

sim_lib_stream_host/
glasses_placement.rs

1//! Placement planning for glasses encoders and latency-critical adapters.
2
3use std::fmt;
4
5use sim_kernel::Symbol;
6
7use crate::{DevicePlacement, DeviceProfile, DeviceSite, DeviceSiteLocality, PlacementError};
8
9/// Local adapter selected for a resolved glasses profile.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum GlassesAdapterKind {
12    /// Stereo pose-coupled Viture reprojector.
13    Reprojector,
14    /// Mono Halo glance adapter.
15    Glance,
16    /// Display-only mirror adapter.
17    Mirror,
18    /// Ordinary HID-to-Intent adapter.
19    ControllerIntent,
20}
21
22impl GlassesAdapterKind {
23    /// Returns the stable adapter-site token.
24    pub const fn token(self) -> &'static str {
25        match self {
26            Self::Reprojector => "glasses-reprojector",
27            Self::Glance => "glasses-glance-adapter",
28            Self::Mirror => "glasses-mirror-adapter",
29            Self::ControllerIntent => "glasses-controller-intent",
30        }
31    }
32
33    /// Returns the surface codec selected by this adapter.
34    pub fn surface_codec_id(self) -> Symbol {
35        let name = match self {
36            Self::Reprojector => "scene-spatial",
37            Self::Glance => "scene-glance",
38            Self::Mirror => "scene-mirror",
39            Self::ControllerIntent => "intent",
40        };
41        Symbol::qualified("codec", name)
42    }
43}
44
45/// Placement request for one already-resolved glasses route.
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct GlassesPlacementRequest {
48    /// Stable `device/route` symbol selected by profile resolution.
49    pub route: Symbol,
50    /// Stream-facing provider profile carried by both sites.
51    pub profile: DeviceProfile,
52    /// Locality chosen for the non-latency-critical encoder.
53    pub encoder_locality: DeviceSiteLocality,
54    /// Latency-critical local adapter implementation.
55    pub adapter: GlassesAdapterKind,
56}
57
58impl GlassesPlacementRequest {
59    /// Builds a placement request.
60    pub fn new(
61        route: Symbol,
62        profile: DeviceProfile,
63        encoder_locality: DeviceSiteLocality,
64        adapter: GlassesAdapterKind,
65    ) -> Self {
66        Self {
67            route,
68            profile,
69            encoder_locality,
70            adapter,
71        }
72    }
73
74    /// Resolves and validates the placement plan.
75    pub fn resolve(self) -> Result<ResolvedGlassesPlacement, PlacementError> {
76        let codec = self.adapter.surface_codec_id();
77        let encoder = DeviceSite::new(
78            route_site_symbol(&self.route, "encoder"),
79            self.profile.clone(),
80            codec.clone(),
81            self.encoder_locality,
82        );
83        let adapter = DeviceSite::edge_local(
84            Symbol::qualified("device/site", self.adapter.token()),
85            self.profile,
86            codec,
87        );
88        let placement = DevicePlacement::new(encoder, adapter);
89        placement.validate()?;
90        Ok(ResolvedGlassesPlacement {
91            route: self.route,
92            adapter: self.adapter,
93            placement,
94        })
95    }
96}
97
98/// Validated placement for one glasses route.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct ResolvedGlassesPlacement {
101    /// Stable route symbol.
102    pub route: Symbol,
103    /// Adapter selected for the resolved device tier.
104    pub adapter: GlassesAdapterKind,
105    /// Validated encoder and edge-local adapter placement.
106    pub placement: DevicePlacement,
107}
108
109/// Error returned while resolving a glasses route placement.
110#[derive(Clone, Debug, PartialEq, Eq)]
111pub enum GlassesPlacementError {
112    /// The route token is not part of the glasses route contract.
113    UnknownRoute(Symbol),
114    /// The resulting device placement violated a locality invariant.
115    InvalidPlacement(PlacementError),
116}
117
118impl fmt::Display for GlassesPlacementError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Self::UnknownRoute(route) => write!(f, "unknown glasses route: {route}"),
122            Self::InvalidPlacement(error) => error.fmt(f),
123        }
124    }
125}
126
127impl std::error::Error for GlassesPlacementError {}
128
129impl From<PlacementError> for GlassesPlacementError {
130    fn from(error: PlacementError) -> Self {
131        Self::InvalidPlacement(error)
132    }
133}
134
135/// Resolves a stable glasses route symbol to a validated placement.
136pub fn resolve_glasses_placement(
137    route: Symbol,
138    profile: DeviceProfile,
139) -> Result<ResolvedGlassesPlacement, GlassesPlacementError> {
140    if route.namespace.as_deref() != Some("device/route") {
141        return Err(GlassesPlacementError::UnknownRoute(route));
142    }
143    let (encoder_locality, adapter) = match route.name.as_ref() {
144        "direct-linux" | "android-usb" => (
145            DeviceSiteLocality::HostLocal,
146            GlassesAdapterKind::Reprojector,
147        ),
148        "neckband-local" => (
149            DeviceSiteLocality::EdgeLocal,
150            GlassesAdapterKind::Reprojector,
151        ),
152        "neckband-relay" => (DeviceSiteLocality::Remote, GlassesAdapterKind::Reprojector),
153        "mobile-dock-display" => (DeviceSiteLocality::HostLocal, GlassesAdapterKind::Mirror),
154        "ble-direct" | "web-bluetooth" => {
155            (DeviceSiteLocality::HostLocal, GlassesAdapterKind::Glance)
156        }
157        "phone-relay" => (DeviceSiteLocality::Remote, GlassesAdapterKind::Glance),
158        "controller-hid" => (
159            DeviceSiteLocality::HostLocal,
160            GlassesAdapterKind::ControllerIntent,
161        ),
162        _ => return Err(GlassesPlacementError::UnknownRoute(route)),
163    };
164    GlassesPlacementRequest::new(route, profile, encoder_locality, adapter)
165        .resolve()
166        .map_err(Into::into)
167}
168
169fn route_site_symbol(route: &Symbol, role: &str) -> Symbol {
170    Symbol::qualified("device/site", format!("{}-{role}", route.name))
171}