use sim_kernel::{Expr, NumberLiteral};
use sim_value::{access, build};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RateClass {
pub content_hz: u16,
pub adapt_hz: u16,
pub max_stale_ms: u16,
}
impl RateClass {
pub fn safe_default() -> Self {
Self {
content_hz: 1,
adapt_hz: 1,
max_stale_ms: 1000,
}
}
pub fn watch() -> Self {
Self {
content_hz: 1,
adapt_hz: 1,
max_stale_ms: 4000,
}
}
pub fn hud() -> Self {
Self {
content_hz: 5,
adapt_hz: 30,
max_stale_ms: 500,
}
}
pub fn stereo() -> Self {
Self {
content_hz: 60,
adapt_hz: 120,
max_stale_ms: 100,
}
}
pub fn to_expr(self) -> Expr {
build::map(vec![
("content-hz", build::uint(u64::from(self.content_hz))),
("adapt-hz", build::uint(u64::from(self.adapt_hz))),
("max-stale-ms", build::uint(u64::from(self.max_stale_ms))),
])
}
pub fn from_expr(expr: &Expr) -> Result<Self, RateError> {
let Expr::Map(_) = expr else {
return Err(RateError::NotRateMap);
};
Ok(Self {
content_hz: rate_field(expr, "content-hz")?,
adapt_hz: rate_field(expr, "adapt-hz")?,
max_stale_ms: rate_field(expr, "max-stale-ms")?,
})
}
pub fn from_optional_expr(expr: Option<&Expr>) -> Result<Self, RateError> {
match expr {
Some(expr) => Self::from_expr(expr),
None => Ok(Self::safe_default()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RateError {
NotRateMap,
MissingField(&'static str),
BadField(&'static str),
ZeroField(&'static str),
OutOfRange(&'static str),
}
impl core::fmt::Display for RateError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RateError::NotRateMap => write!(f, "rate value is not a map"),
RateError::MissingField(name) => write!(f, "rate map missing field: {name}"),
RateError::BadField(name) => write!(f, "rate field has wrong shape: {name}"),
RateError::ZeroField(name) => write!(f, "rate field must be non-zero: {name}"),
RateError::OutOfRange(name) => write!(f, "rate field exceeds u16: {name}"),
}
}
}
impl std::error::Error for RateError {}
fn rate_field(expr: &Expr, name: &'static str) -> Result<u16, RateError> {
let Some(value) = access::field(expr, name) else {
return Err(RateError::MissingField(name));
};
let Some(number) = integer_number(value) else {
return Err(RateError::BadField(name));
};
let parsed = number
.canonical
.parse::<u64>()
.map_err(|_| RateError::BadField(name))?;
if parsed == 0 {
return Err(RateError::ZeroField(name));
}
u16::try_from(parsed).map_err(|_| RateError::OutOfRange(name))
}
fn integer_number(expr: &Expr) -> Option<&NumberLiteral> {
match expr {
Expr::Number(number)
if number.domain.namespace.is_none()
&& matches!(number.domain.name.as_ref(), "i64" | "u64") =>
{
Some(number)
}
_ => None,
}
}