use super::*;
pub trait TrackedStateMethods {
fn mark_fresh<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
fn check_and_reset<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()>;
}
impl<T> TrackedStateMethods for TrackedState<T>
where
T: std::fmt::Debug + Clone + PartialEq + Default,
{
fn check_and_reset<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
self.ensure_fresh(loc)?;
self.mark_stale();
Ok(())
}
fn mark_fresh<F: Fn() -> String>(&mut self, loc: F) -> anyhow::Result<()> {
self.ensure_stale(|| format!("{}\n{}", format_dbg!(), loc()))?;
self.1 = StateStatus::Fresh;
Ok(())
}
}
#[derive(Clone, Default, Debug, PartialEq, IsVariant, derive_more::From, TryInto)]
pub enum StateStatus {
#[default]
Fresh,
Stale,
}
#[derive(Default, PartialEq, Clone, Debug)]
pub struct TrackedState<T>(
T,
StateStatus,
);
impl<T> TrackedState<T>
where
T: std::fmt::Debug + Clone + PartialEq + Default,
{
pub fn new(value: T) -> Self {
Self(value, Default::default())
}
fn is_fresh(&self) -> bool {
self.1.is_fresh()
}
fn is_stale(&self) -> bool {
self.1.is_stale()
}
fn ensure_fresh<F: Fn() -> String>(&self, loc: F) -> anyhow::Result<()> {
ensure!(
self.is_fresh(),
format!(
"{}\nState variable has not been updated. This is a bug in `fastsim-core`",
loc()
)
);
Ok(())
}
fn ensure_stale<F: Fn() -> String>(&self, loc: F) -> anyhow::Result<()> {
ensure!(
self.is_stale(),
format!(
"{}\nState variable has already been updated. This is a bug in `fastsim-core`",
loc()
)
);
Ok(())
}
pub fn mark_stale(&mut self) {
self.1 = StateStatus::Stale;
}
pub fn update<F: Fn() -> String>(&mut self, value: T, loc: F) -> anyhow::Result<()> {
self.ensure_stale(loc)?;
self.0 = value;
self.1 = StateStatus::Fresh;
Ok(())
}
pub fn update_unchecked<F: Fn() -> String>(&mut self, value: T, _loc: F) -> anyhow::Result<()> {
self.0 = value;
self.1 = StateStatus::Fresh;
Ok(())
}
pub fn get_fresh<F: Fn() -> String>(&self, loc: F) -> anyhow::Result<&T> {
self.ensure_fresh(loc)?;
Ok(&self.0)
}
pub fn get_stale<F: Fn() -> String>(&self, loc: F) -> anyhow::Result<&T> {
self.ensure_stale(loc)?;
Ok(&self.0)
}
}
impl<T: std::fmt::Debug + Clone + PartialEq + Default + std::ops::AddAssign> TrackedState<T> {
pub fn increment<F: Fn() -> String>(&mut self, value: T, loc: F) -> anyhow::Result<()> {
self.ensure_stale(loc)?;
self.0 += value;
self.1 = StateStatus::Fresh;
Ok(())
}
}
impl<T> Serialize for TrackedState<T>
where
T: std::fmt::Debug + Clone + PartialEq + for<'de> Deserialize<'de> + Serialize + Default,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de, T> Deserialize<'de> for TrackedState<T>
where
T: std::fmt::Debug + Clone + PartialEq + Deserialize<'de> + Serialize + Default,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: T = T::deserialize(deserializer)?;
Ok(Self(value, Default::default()))
}
}
#[cfg(test)]
mod test_tracked_state {
use super::*;
#[test]
#[should_panic]
fn test_update_fresh() {
let mut pwr = TrackedState::new(si::Power::ZERO);
pwr.update(uc::W * 10.0, || format_dbg!()).unwrap();
}
#[test]
fn test_update_stale() {
let mut pwr = TrackedState::new(si::Power::ZERO);
pwr.mark_stale();
pwr.update(uc::W * 10.0, || format_dbg!()).unwrap();
}
#[test]
fn test_get_ok() {
let mut pwr = TrackedState::new(si::Power::ZERO);
pwr.get_fresh(|| format_dbg!()).unwrap();
pwr.mark_stale();
pwr.get_stale(|| format_dbg!()).unwrap();
}
#[test]
#[should_panic]
fn test_get_stale_fail() {
let pwr = TrackedState::new(si::Power::ZERO);
pwr.get_stale(|| format_dbg!()).unwrap();
}
#[test]
#[should_panic]
fn test_get_fresh_fail() {
let mut pwr = TrackedState::new(si::Power::ZERO);
pwr.mark_stale();
pwr.get_fresh(|| format_dbg!()).unwrap();
}
}