Skip to main content

sim_lib_view_device/
glance_adapter.rs

1//! Local adapter for one-card glance surfaces.
2
3use std::rc::Rc;
4
5use sim_kernel::{Error, Expr, Result};
6use sim_value::build;
7
8use crate::{
9    DeviceProfile, EncodedScene, GlanceBudget, GlanceInput, GlanceState, LocalAdapter,
10    fit_to_budget,
11};
12
13/// The shared one-card adapter for every "one card plus one ack" tier.
14///
15/// Constraint: a device at or below the "one card plus one ack" tier MUST
16/// configure this adapter rather than write its own card reducer. The HUD
17/// glasses tier and watch glance tier share this single type, differing only by
18/// [`GlanceBudget`] and [`crate::AckChannel`].
19///
20/// Soften path: a bespoke [`LocalAdapter`] is reserved for surfaces that are
21/// genuinely richer than a glance, and the adapter introduction must document
22/// the architectural reason. To relax the rule globally, extend
23/// [`GlanceBudget`] with the new capability so one-card tiers stay unified;
24/// drop to a bespoke adapter only after that extension cannot express the need.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct GlanceAdapter {
27    /// Local fitting budget.
28    pub budget: GlanceBudget,
29    /// Ack duration in modeled milliseconds.
30    pub ack_ms: u64,
31}
32
33impl GlanceAdapter {
34    /// Builds an adapter for one glance budget.
35    pub fn new(budget: GlanceBudget, ack_ms: u64) -> Self {
36        Self { budget, ack_ms }
37    }
38}
39
40impl LocalAdapter for GlanceAdapter {
41    type State = GlanceState;
42
43    fn adapt(
44        &self,
45        scene: &EncodedScene,
46        state: &Self::State,
47        _profile: &DeviceProfile,
48    ) -> Result<Rc<Expr>> {
49        if !crate::glance::is_glance(scene.expr()) {
50            return Err(Error::HostError(
51                "GlanceAdapter expects an encoded scene/glance card".to_owned(),
52            ));
53        }
54        let card = fit_to_budget(scene.expr(), &self.budget)?;
55        Ok(Rc::new(match state.pending_input {
56            Some(input) => with_ack(card, input, state.tick, self.budget, self.ack_ms),
57            None => card,
58        }))
59    }
60}
61
62fn with_ack(card: Expr, input: GlanceInput, tick: u64, budget: GlanceBudget, ack_ms: u64) -> Expr {
63    sim_value::access::set(
64        &sim_value::access::set(
65            &sim_value::access::set(
66                &sim_value::access::set(&card, "ack-channel", Expr::Symbol(budget.ack.to_symbol())),
67                "ack-input",
68                build::sym(input.token()),
69            ),
70            "ack-ms",
71            build::uint(ack_ms),
72        ),
73        "ack-tick",
74        build::uint(tick),
75    )
76}