#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::trace::TrainingEvent;
use crate::training::TrainingOutcome;
pub const MAX_WARNINGS: usize = 5;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrainingWarning {
#[non_exhaustive]
UndefinedLtpRequest {
lane: u8,
value: u8,
count: u32,
in_use: bool,
},
}
#[non_exhaustive]
#[derive(Clone)]
pub struct Trained {
pub outcome: TrainingOutcome,
#[cfg(feature = "alloc")]
warnings: Vec<TrainingWarning>,
#[cfg(not(feature = "alloc"))]
warnings: [TrainingWarning; MAX_WARNINGS],
#[cfg(not(feature = "alloc"))]
len: usize,
}
#[cfg(not(feature = "alloc"))]
const UNUSED: TrainingWarning = TrainingWarning::UndefinedLtpRequest {
lane: 0,
value: 0,
count: 0,
in_use: false,
};
impl Trained {
pub(crate) fn new(outcome: TrainingOutcome) -> Self {
Self {
outcome,
#[cfg(feature = "alloc")]
warnings: Vec::new(),
#[cfg(not(feature = "alloc"))]
warnings: [UNUSED; MAX_WARNINGS],
#[cfg(not(feature = "alloc"))]
len: 0,
}
}
pub fn warnings(&self) -> &[TrainingWarning] {
#[cfg(feature = "alloc")]
let warnings = &self.warnings;
#[cfg(not(feature = "alloc"))]
let warnings = &self.warnings[..self.len];
warnings
}
pub fn iter_warnings(&self) -> impl Iterator<Item = &TrainingWarning> {
self.warnings().iter()
}
fn warnings_mut(&mut self) -> &mut [TrainingWarning] {
#[cfg(feature = "alloc")]
let warnings = &mut self.warnings;
#[cfg(not(feature = "alloc"))]
let warnings = &mut self.warnings[..self.len];
warnings
}
fn push(&mut self, warning: TrainingWarning) {
#[cfg(feature = "alloc")]
self.warnings.push(warning);
#[cfg(not(feature = "alloc"))]
if let Some(slot) = self.warnings.get_mut(self.len) {
*slot = warning;
self.len += 1;
}
}
pub(crate) fn observe(&mut self, event: &TrainingEvent) {
if let TrainingEvent::UndefinedLtpRequest {
lane,
value,
in_use,
} = *event
{
let earlier = self
.warnings_mut()
.iter_mut()
.find_map(|warning| match warning {
TrainingWarning::UndefinedLtpRequest {
lane: l,
value,
count,
in_use: u,
} if *l == lane && *u == in_use => Some((value, count)),
_ => None,
});
match earlier {
Some((last, count)) => {
*last = value;
*count = count.saturating_add(1);
}
None => self.push(TrainingWarning::UndefinedLtpRequest {
lane,
value,
count: 1,
in_use,
}),
}
}
}
}
impl core::fmt::Debug for Trained {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Trained")
.field("outcome", &self.outcome)
.field("warnings", &self.warnings())
.finish()
}
}
impl PartialEq for Trained {
fn eq(&self, other: &Self) -> bool {
self.outcome == other.outcome && self.warnings() == other.warnings()
}
}
impl Eq for Trained {}
#[cfg(test)]
mod tests {
use super::*;
use crate::training::FallbackReason;
fn undefined(lane: u8, value: u8, in_use: bool) -> TrainingEvent {
TrainingEvent::UndefinedLtpRequest {
lane,
value,
in_use,
}
}
#[test]
fn the_repeat_count_saturates() {
let mut trained = Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::TrainingTimeout,
});
trained.observe(&undefined(0, 0x9, true));
let TrainingWarning::UndefinedLtpRequest { count, .. } = &mut trained.warnings_mut()[0];
*count = u32::MAX;
trained.observe(&undefined(0, 0x9, true));
assert!(matches!(
trained.iter_warnings().next(),
Some(TrainingWarning::UndefinedLtpRequest {
count: u32::MAX,
..
})
));
}
#[test]
fn repeats_on_a_lane_are_merged_with_the_last_value() {
let mut trained = Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::TrainingTimeout,
});
trained.observe(&undefined(1, 0x9, true));
trained.observe(&TrainingEvent::ExitedToTmds);
trained.observe(&undefined(2, 0xA, true));
trained.observe(&undefined(1, 0xC, true));
trained.observe(&undefined(3, 0xB, false));
trained.observe(&undefined(3, 0xB, true));
let warnings: [TrainingWarning; 4] = [
TrainingWarning::UndefinedLtpRequest {
lane: 1,
value: 0xC,
count: 2,
in_use: true,
},
TrainingWarning::UndefinedLtpRequest {
lane: 2,
value: 0xA,
count: 1,
in_use: true,
},
TrainingWarning::UndefinedLtpRequest {
lane: 3,
value: 0xB,
count: 1,
in_use: false,
},
TrainingWarning::UndefinedLtpRequest {
lane: 3,
value: 0xB,
count: 1,
in_use: true,
},
];
assert!(trained.iter_warnings().eq(warnings.iter()));
}
#[test]
fn every_distinct_warning_fits() {
let mut trained = Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::TrainingTimeout,
});
for (lane, in_use) in [(0, true), (1, true), (2, true), (3, true), (3, false)] {
for _ in 0..100 {
trained.observe(&undefined(lane, 0x9, in_use));
}
}
assert_eq!(trained.iter_warnings().count(), 5);
const { assert!(5 <= MAX_WARNINGS) };
}
fn timed_out() -> Trained {
Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::TrainingTimeout,
})
}
#[test]
fn the_slice_and_the_iterator_agree() {
let mut trained = timed_out();
assert!(trained.warnings().is_empty());
trained.observe(&undefined(1, 0x9, true));
trained.observe(&undefined(2, 0xA, true));
assert_eq!(trained.warnings().len(), 2);
assert!(trained.iter_warnings().eq(trained.warnings().iter()));
}
#[test]
fn equality_compares_the_outcome_and_the_warnings_in_use() {
let (mut a, mut b) = (timed_out(), timed_out());
assert_eq!(a, b);
a.observe(&undefined(1, 0x9, true));
assert_ne!(a, b);
b.observe(&undefined(1, 0x9, true));
assert_eq!(a, b);
let c = Trained::new(TrainingOutcome::FallbackRequired {
reason: FallbackReason::RatesExhausted,
});
assert_ne!(timed_out(), c);
}
#[test]
fn debug_shows_the_outcome_and_only_the_warnings_in_use() {
extern crate std;
use std::format;
let mut trained = timed_out();
trained.observe(&undefined(1, 0x9, true));
let debug = format!("{trained:?}");
assert!(debug.starts_with("Trained { outcome: FallbackRequired"));
assert_eq!(debug.matches("UndefinedLtpRequest").count(), 1);
}
}