sim_lib_view_device/
rate.rs1use sim_kernel::{Expr, NumberLiteral};
4use sim_value::{access, build};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct RateClass {
9 pub content_hz: u16,
11 pub adapt_hz: u16,
13 pub max_stale_ms: u16,
15}
16
17impl RateClass {
18 pub fn safe_default() -> Self {
20 Self {
21 content_hz: 1,
22 adapt_hz: 1,
23 max_stale_ms: 1000,
24 }
25 }
26
27 pub fn watch() -> Self {
29 Self::safe_default()
30 }
31
32 pub fn hud() -> Self {
34 Self {
35 content_hz: 5,
36 adapt_hz: 30,
37 max_stale_ms: 500,
38 }
39 }
40
41 pub fn stereo() -> Self {
43 Self {
44 content_hz: 60,
45 adapt_hz: 120,
46 max_stale_ms: 100,
47 }
48 }
49
50 pub fn to_expr(self) -> Expr {
52 build::map(vec![
53 ("content-hz", build::uint(u64::from(self.content_hz))),
54 ("adapt-hz", build::uint(u64::from(self.adapt_hz))),
55 ("max-stale-ms", build::uint(u64::from(self.max_stale_ms))),
56 ])
57 }
58
59 pub fn from_expr(expr: &Expr) -> Result<Self, RateError> {
61 let Expr::Map(_) = expr else {
62 return Err(RateError::NotRateMap);
63 };
64 Ok(Self {
65 content_hz: rate_field(expr, "content-hz")?,
66 adapt_hz: rate_field(expr, "adapt-hz")?,
67 max_stale_ms: rate_field(expr, "max-stale-ms")?,
68 })
69 }
70
71 pub fn from_optional_expr(expr: Option<&Expr>) -> Result<Self, RateError> {
74 match expr {
75 Some(expr) => Self::from_expr(expr),
76 None => Ok(Self::safe_default()),
77 }
78 }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum RateError {
84 NotRateMap,
86 MissingField(&'static str),
88 BadField(&'static str),
90 ZeroField(&'static str),
92 OutOfRange(&'static str),
94}
95
96impl core::fmt::Display for RateError {
97 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
98 match self {
99 RateError::NotRateMap => write!(f, "rate value is not a map"),
100 RateError::MissingField(name) => write!(f, "rate map missing field: {name}"),
101 RateError::BadField(name) => write!(f, "rate field has wrong shape: {name}"),
102 RateError::ZeroField(name) => write!(f, "rate field must be non-zero: {name}"),
103 RateError::OutOfRange(name) => write!(f, "rate field exceeds u16: {name}"),
104 }
105 }
106}
107
108impl std::error::Error for RateError {}
109
110fn rate_field(expr: &Expr, name: &'static str) -> Result<u16, RateError> {
111 let Some(value) = access::field(expr, name) else {
112 return Err(RateError::MissingField(name));
113 };
114 let Some(number) = integer_number(value) else {
115 return Err(RateError::BadField(name));
116 };
117 let parsed = number
118 .canonical
119 .parse::<u64>()
120 .map_err(|_| RateError::BadField(name))?;
121 if parsed == 0 {
122 return Err(RateError::ZeroField(name));
123 }
124 u16::try_from(parsed).map_err(|_| RateError::OutOfRange(name))
125}
126
127fn integer_number(expr: &Expr) -> Option<&NumberLiteral> {
128 match expr {
129 Expr::Number(number)
130 if number.domain.namespace.is_none()
131 && matches!(number.domain.name.as_ref(), "i64" | "u64") =>
132 {
133 Some(number)
134 }
135 _ => None,
136 }
137}