apsis-rs/crates/sim/apsis-output/src/recorder.rs

109 lines
3.2 KiB
Rust

//! Building a trace as a simulation runs.
use crate::record::{Record, RecordKind};
use crate::trace::Trace;
/// Collects records as a simulation runs.
///
/// The recorder is the write side of a trace: a handler calls one of its
/// methods for each value it wants to keep, and asks for the trace when the
/// run finishes. The two are separate so that a handler can hold a recorder
/// while the trace is read elsewhere.
///
/// ```
/// use apsis_output::Recorder;
///
/// let mut recorder = Recorder::new();
/// recorder.observe(0.0, "range", 1_000.0);
/// recorder.observe(10.0, "range", 950.0);
///
/// let summary = recorder.trace().summarize("range").expect("the series exists");
/// assert_eq!(summary.count, 2);
/// assert_eq!(summary.change, -50.0);
/// ```
#[derive(Debug, Clone, Default)]
pub struct Recorder {
trace: Trace,
refused: u64,
}
impl Recorder {
/// Creates an empty recorder.
pub const fn new() -> Self {
Self {
trace: Trace::new(),
refused: 0,
}
}
/// Records a value of a given kind.
///
/// Returns `false` when the record is not usable, which a caller can
/// ignore when the value is known to be finite.
pub fn record(
&mut self,
time: f64,
kind: RecordKind,
label: impl Into<String>,
value: f64,
) -> bool {
let accepted = self.trace.push(Record::new(time, kind, label, value));
if !accepted {
self.refused = self.refused.wrapping_add(1);
}
accepted
}
/// Records an observation.
pub fn observe(&mut self, time: f64, label: impl Into<String>, value: f64) -> bool {
self.record(time, RecordKind::Observation, label, value)
}
/// Records a state change.
pub fn note(&mut self, time: f64, label: impl Into<String>, value: f64) -> bool {
self.record(time, RecordKind::State, label, value)
}
/// Records a warning.
pub fn warn(&mut self, time: f64, label: impl Into<String>, value: f64) -> bool {
self.record(time, RecordKind::Warning, label, value)
}
/// Returns the number of records made.
pub fn len(&self) -> usize {
self.trace.len()
}
/// Returns `true` when no records have been made.
pub fn is_empty(&self) -> bool {
self.trace.is_empty()
}
/// Returns the number of records refused because they were not usable.
///
/// A caller that records values from a model checks this to see whether
/// any value was dropped, rather than comparing counts by hand.
pub const fn refused(&self) -> u64 {
self.refused
}
/// Returns the trace recorded so far.
pub const fn trace(&self) -> &Trace {
&self.trace
}
/// Returns the trace, leaving an empty one behind.
///
/// This is what a caller does between runs: the finished trace goes to
/// whoever reads it, and the recorder starts fresh. The refused count is
/// not reset, because it records the whole life of the recorder.
pub fn take_trace(&mut self) -> Trace {
core::mem::take(&mut self.trace)
}
/// Removes every record without changing the refused count.
pub fn clear(&mut self) -> usize {
self.trace.clear()
}
}