sim_lib_view_spatial/
co_use.rs1use sim_kernel::{Error, Result};
4use sim_lib_view::SurfaceCaps;
5use sim_lib_view_device::{
6 ConsentReceipt, DeviceProfile, DeviceSurfaceCapsExt, EdgeId, GlassesClass, StalePolicy,
7 glasses_class,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum GlassesPeer {
13 Viture,
15 Halo,
17}
18
19impl GlassesPeer {
20 pub fn role(self) -> GlassesCoUseRole {
22 match self {
23 Self::Viture => GlassesCoUseRole::Main,
24 Self::Halo => GlassesCoUseRole::Peer,
25 }
26 }
27
28 pub fn expected_class(self) -> GlassesClass {
30 match self {
31 Self::Viture => GlassesClass::Stereo6Dof,
32 Self::Halo => GlassesClass::MonoHud,
33 }
34 }
35
36 pub fn label(self) -> &'static str {
38 match self {
39 Self::Viture => "viture",
40 Self::Halo => "halo",
41 }
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum GlassesCoUseRole {
48 Main,
50 Peer,
52}
53
54#[derive(Clone, Debug, PartialEq)]
56pub struct GlassesPeerConfig {
57 pub peer: GlassesPeer,
59 pub role: GlassesCoUseRole,
61 pub profile: DeviceProfile,
63 pub policy: StalePolicy,
65}
66
67#[derive(Clone, Debug, PartialEq)]
69pub struct GlassesCoUsePlan {
70 session: EdgeId,
71 consent: ConsentReceipt,
72 peers: Vec<GlassesPeerConfig>,
73}
74
75impl GlassesCoUsePlan {
76 pub fn new(session: EdgeId, consent: ConsentReceipt) -> Result<Self> {
78 if consent.session != session {
79 return Err(Error::HostError(
80 "glasses co-use consent receipt is bound to a different session".to_owned(),
81 ));
82 }
83 Ok(Self {
84 session,
85 consent,
86 peers: Vec::new(),
87 })
88 }
89
90 pub fn attach_caps(
92 &mut self,
93 peer: GlassesPeer,
94 caps: &SurfaceCaps,
95 ) -> Result<GlassesPeerConfig> {
96 let config = glasses_peer_config(peer, caps)?;
97 self.peers.retain(|existing| existing.peer != peer);
98 self.peers.push(config.clone());
99 Ok(config)
100 }
101
102 pub fn detach(&mut self, peer: GlassesPeer) -> Option<GlassesPeerConfig> {
104 let index = self
105 .peers
106 .iter()
107 .position(|existing| existing.peer == peer)?;
108 Some(self.peers.remove(index))
109 }
110
111 pub fn is_alive(&self) -> bool {
113 !self.peers.is_empty()
114 }
115
116 pub fn session(&self) -> &EdgeId {
118 &self.session
119 }
120
121 pub fn consent(&self) -> &ConsentReceipt {
123 &self.consent
124 }
125
126 pub fn peers(&self) -> &[GlassesPeerConfig] {
128 &self.peers
129 }
130}
131
132pub fn glasses_peer_config(peer: GlassesPeer, caps: &SurfaceCaps) -> Result<GlassesPeerConfig> {
134 let profile = caps.device_profile();
135 let class = glasses_class(&profile).ok_or_else(|| {
136 Error::HostError(format!("{} caps do not describe glasses", peer.label()))
137 })?;
138 if class != peer.expected_class() {
139 return Err(Error::HostError(format!(
140 "{} caps resolve to {class:?}, expected {:?}",
141 peer.label(),
142 peer.expected_class()
143 )));
144 }
145 Ok(GlassesPeerConfig {
146 peer,
147 role: peer.role(),
148 profile,
149 policy: peer_policy(peer),
150 })
151}
152
153fn peer_policy(peer: GlassesPeer) -> StalePolicy {
154 match peer {
155 GlassesPeer::Viture => StalePolicy::Predict,
156 GlassesPeer::Halo => StalePolicy::HoldLast,
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 use sim_lib_view_device::DeviceCapability;
165
166 use crate::{halo_loop, viture_loop};
167
168 #[test]
169 fn viture_and_halo_share_one_session() {
170 let edge = EdgeId::named("wear-session");
171 let consent = ConsentReceipt::new(
172 vec![
173 DeviceCapability::Pose.grant_symbol(),
174 DeviceCapability::Mic.grant_symbol(),
175 ],
176 60_000,
177 Vec::new(),
178 edge.clone(),
179 9,
180 );
181 let mut plan = GlassesCoUsePlan::new(edge.clone(), consent.clone()).unwrap();
182
183 let viture_caps = SurfaceCaps::from_preset("glasses-luma-ultra", "viture.co-use").unwrap();
184 let halo_caps = SurfaceCaps::from_preset("glasses-hud", "halo.co-use").unwrap();
185 let viture = plan.attach_caps(GlassesPeer::Viture, &viture_caps).unwrap();
186 let halo = plan.attach_caps(GlassesPeer::Halo, &halo_caps).unwrap();
187
188 assert_eq!(plan.session(), &edge);
189 assert_eq!(plan.consent(), &consent);
190 assert_eq!(plan.peers().len(), 2);
191 assert_eq!(viture.role, GlassesCoUseRole::Main);
192 assert_eq!(halo.role, GlassesCoUseRole::Peer);
193 assert_eq!(viture.policy, StalePolicy::Predict);
194 assert_eq!(halo.policy, StalePolicy::HoldLast);
195 assert_eq!(viture_loop(&viture.profile, 12).0.policy(), viture.policy);
196 assert_eq!(halo_loop(&halo.profile).0.policy(), halo.policy);
197
198 assert!(plan.detach(GlassesPeer::Viture).is_some());
199 assert!(plan.is_alive(), "Halo keeps the shared session alive");
200 assert_eq!(plan.peers()[0].peer, GlassesPeer::Halo);
201 assert_eq!(plan.session(), &edge);
202 assert_eq!(plan.consent(), &consent);
203 }
204}