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 {
30 content_hz: 1,
31 adapt_hz: 1,
32 max_stale_ms: 4000,
33 }
34 }
35
36 pub fn hud() -> Self {
38 Self {
39 content_hz: 5,
40 adapt_hz: 30,
41 max_stale_ms: 500,
42 }
43 }
44
45 pub fn stereo() -> Self {
47 Self {
48 content_hz: 60,
49 adapt_hz: 120,
50 max_stale_ms: 100,
51 }
52 }
53
54 pub fn to_expr(self) -> Expr {
56 build::map(vec![
57 ("content-hz", build::uint(u64::from(self.content_hz))),
58 ("adapt-hz", build::uint(u64::from(self.adapt_hz))),
59 ("max-stale-ms", build::uint(u64::from(self.max_stale_ms))),
60 ])
61 }
62
63 pub fn from_expr(expr: &Expr) -> Result<Self, RateError> {
65 let Expr::Map(_) = expr else {
66 return Err(RateError::NotRateMap);
67 };
68 Ok(Self {
69 content_hz: rate_field(expr, "content-hz")?,
70 adapt_hz: rate_field(expr, "adapt-hz")?,
71 max_stale_ms: rate_field(expr, "max-stale-ms")?,
72 })
73 }
74
75 pub fn from_optional_expr(expr: Option<&Expr>) -> Result<Self, RateError> {
78 match expr {
79 Some(expr) => Self::from_expr(expr),
80 None => Ok(Self::safe_default()),
81 }
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
87pub enum RateError {
88 NotRateMap,
90 MissingField(&'static str),
92 BadField(&'static str),
94 ZeroField(&'static str),
96 OutOfRange(&'static str),
98}
99
100impl core::fmt::Display for RateError {
101 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102 match self {
103 RateError::NotRateMap => write!(f, "rate value is not a map"),
104 RateError::MissingField(name) => write!(f, "rate map missing field: {name}"),
105 RateError::BadField(name) => write!(f, "rate field has wrong shape: {name}"),
106 RateError::ZeroField(name) => write!(f, "rate field must be non-zero: {name}"),
107 RateError::OutOfRange(name) => write!(f, "rate field exceeds u16: {name}"),
108 }
109 }
110}
111
112impl std::error::Error for RateError {}
113
114fn rate_field(expr: &Expr, name: &'static str) -> Result<u16, RateError> {
115 let Some(value) = access::field(expr, name) else {
116 return Err(RateError::MissingField(name));
117 };
118 let Some(number) = integer_number(value) else {
119 return Err(RateError::BadField(name));
120 };
121 let parsed = number
122 .canonical
123 .parse::<u64>()
124 .map_err(|_| RateError::BadField(name))?;
125 if parsed == 0 {
126 return Err(RateError::ZeroField(name));
127 }
128 u16::try_from(parsed).map_err(|_| RateError::OutOfRange(name))
129}
130
131fn integer_number(expr: &Expr) -> Option<&NumberLiteral> {
132 match expr {
133 Expr::Number(number)
134 if number.domain.namespace.is_none()
135 && matches!(number.domain.name.as_ref(), "i64" | "u64") =>
136 {
137 Some(number)
138 }
139 _ => None,
140 }
141}