use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
use sim_lib_view_device::{ConsentReceipt, EdgeId, require_with_consent};
use sim_value::{access, build};
use crate::{BoundedContentStore, ContentFrame, RetentionWindow, StoreEvicted, StoreKey};
pub const CAP_GLASSES_POSE: &str = "glasses/pose";
pub const CAP_GLASSES_CAMERA: &str = "glasses/camera";
pub const CAP_GLASSES_WORLD_ANCHOR: &str = "glasses/world-anchor";
pub const CAP_GLASSES_HAND: &str = "glasses/hand";
pub const CAP_GLASSES_MIC: &str = "glasses/mic";
pub const CAP_GLASSES_VENDOR_REPORT: &str = "glasses/vendor-report";
const XR_NAMESPACE: &str = "xr";
const DEVICE_SAMPLE_NAMESPACE: &str = "stream/device-sample";
const GLASSES_NAMESPACE: &str = "glasses";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GlassesCapability {
Pose,
Camera,
WorldAnchor,
Hand,
Mic,
VendorReport,
}
impl GlassesCapability {
pub const ALL: [Self; 6] = [
Self::Pose,
Self::Camera,
Self::WorldAnchor,
Self::Hand,
Self::Mic,
Self::VendorReport,
];
pub fn as_str(self) -> &'static str {
match self {
Self::Pose => CAP_GLASSES_POSE,
Self::Camera => CAP_GLASSES_CAMERA,
Self::WorldAnchor => CAP_GLASSES_WORLD_ANCHOR,
Self::Hand => CAP_GLASSES_HAND,
Self::Mic => CAP_GLASSES_MIC,
Self::VendorReport => CAP_GLASSES_VENDOR_REPORT,
}
}
pub fn local_name(self) -> &'static str {
match self {
Self::Pose => "pose",
Self::Camera => "camera",
Self::WorldAnchor => "world-anchor",
Self::Hand => "hand",
Self::Mic => "mic",
Self::VendorReport => "vendor-report",
}
}
pub fn capability_name(self) -> CapabilityName {
CapabilityName::new(self.as_str())
}
pub fn grant_symbol(self) -> Symbol {
Symbol::qualified(GLASSES_NAMESPACE, self.local_name())
}
pub fn from_name(name: &str) -> Option<Self> {
Self::ALL
.into_iter()
.find(|capability| capability.as_str() == name)
}
}
pub fn glasses_pose_grant() -> Symbol {
GlassesCapability::Pose.grant_symbol()
}
pub fn glasses_camera_grant() -> Symbol {
GlassesCapability::Camera.grant_symbol()
}
pub fn glasses_world_anchor_grant() -> Symbol {
GlassesCapability::WorldAnchor.grant_symbol()
}
pub fn glasses_hand_grant() -> Symbol {
GlassesCapability::Hand.grant_symbol()
}
pub fn glasses_mic_grant() -> Symbol {
GlassesCapability::Mic.grant_symbol()
}
pub fn glasses_vendor_report_grant() -> Symbol {
GlassesCapability::VendorReport.grant_symbol()
}
pub fn glasses_capability_for_sample(sample: &Expr) -> Result<GlassesCapability> {
if access::field(sample, "world-anchor").is_some() {
return Ok(GlassesCapability::WorldAnchor);
}
let symbol = access::field_sym(sample, "sample")
.or_else(|| access::field_sym(sample, "kind"))
.ok_or_else(|| Error::Eval("missing glasses sample or kind field".to_owned()))?;
match (symbol.namespace.as_deref(), symbol.name.as_ref()) {
(Some(XR_NAMESPACE), "pose") => Ok(GlassesCapability::Pose),
(Some(XR_NAMESPACE), "camera-frame") => Ok(GlassesCapability::Camera),
(Some(XR_NAMESPACE), "hand") => Ok(GlassesCapability::Hand),
(Some(XR_NAMESPACE), "mic-chunk") => Ok(GlassesCapability::Mic),
(Some(DEVICE_SAMPLE_NAMESPACE), "xr/pose") => Ok(GlassesCapability::Pose),
(Some(DEVICE_SAMPLE_NAMESPACE), "xr/camera-frame") => Ok(GlassesCapability::Camera),
(Some(DEVICE_SAMPLE_NAMESPACE), "xr/hand") => Ok(GlassesCapability::Hand),
(Some(DEVICE_SAMPLE_NAMESPACE), "xr/mic-chunk") => Ok(GlassesCapability::Mic),
(Some(GLASSES_NAMESPACE), "world-anchor") => Ok(GlassesCapability::WorldAnchor),
(Some(GLASSES_NAMESPACE), "vendor-report") => Ok(GlassesCapability::VendorReport),
_ => Err(Error::HostError(format!(
"no glasses consent capability for {}",
symbol.as_qualified_str()
))),
}
}
pub fn require_glasses_consent(
cx: &Cx,
capability: GlassesCapability,
receipt: &ConsentReceipt,
session: &EdgeId,
) -> Result<()> {
require_with_consent(cx, capability.as_str(), receipt, session)
}
pub fn require_glasses_sample_ingest(
cx: &Cx,
sample: &Expr,
receipt: &ConsentReceipt,
session: &EdgeId,
) -> Result<GlassesCapability> {
let capability = glasses_capability_for_sample(sample)?;
require_glasses_consent(cx, capability, receipt, session)?;
Ok(capability)
}
pub fn store_glasses_frame(
store: &mut BoundedContentStore,
capability: GlassesCapability,
ref_id: Symbol,
value: Expr,
receipt: &ConsentReceipt,
inserted_tick: u64,
size_bytes: usize,
) -> Result<(StoreKey, Vec<StoreEvicted>)> {
let key = StoreKey::new(ref_id);
let evicted = store.insert(ContentFrame::new(
key.clone(),
receipt.session.as_symbol().clone(),
receipt.seq,
inserted_tick,
size_bytes,
stored_glasses_value(capability, value),
))?;
Ok((key, evicted))
}
pub fn glasses_retention_windows(receipts: &[ConsentReceipt]) -> Vec<RetentionWindow> {
receipts
.iter()
.filter(|receipt| {
receipt
.grants
.iter()
.any(|grant| grant.namespace.as_deref() == Some(GLASSES_NAMESPACE))
})
.map(|receipt| {
RetentionWindow::new(
receipt.session.as_symbol().clone(),
receipt.seq,
receipt.retain_ms,
)
})
.collect()
}
pub fn sweep_glasses_retention(
store: &mut BoundedContentStore,
receipts: &[ConsentReceipt],
now_tick: u64,
adapt_hz: u16,
) -> Vec<StoreEvicted> {
store.sweep_retention(now_tick, adapt_hz, &glasses_retention_windows(receipts))
}
fn stored_glasses_value(capability: GlassesCapability, value: Expr) -> Expr {
build::map(vec![
("kind", build::qsym("glasses", "content-frame")),
("capability", Expr::Symbol(capability.grant_symbol())),
(capability.local_name(), value),
])
}