#[derive(Debug, Clone, Copy)]
pub struct AuditEvent {
pub timestamp_ns: u64,
pub residual: f64,
pub drift: f64,
pub slew: f64,
pub envelope_position: u8, pub grammar_state: u8, pub transition_occurred: bool,
}
pub struct AuditTrace {
events: [AuditEvent; 256],
head: usize,
total_count: u64,
}
impl AuditTrace {
pub fn new() -> Self {
Self {
events: [AuditEvent {
timestamp_ns: 0,
residual: 0.0,
drift: 0.0,
slew: 0.0,
envelope_position: 0,
grammar_state: 0,
transition_occurred: false,
}; 256],
head: 0,
total_count: 0,
}
}
pub fn record(&mut self, event: AuditEvent) {
self.events[self.head] = event;
self.head = (self.head + 1) % 256;
self.total_count += 1;
}
pub fn total_count(&self) -> u64 {
self.total_count
}
pub fn buffered_count(&self) -> usize {
if self.total_count < 256 {
self.total_count as usize
} else {
256
}
}
pub fn iter(&self) -> AuditTraceIter<'_> {
let count = self.buffered_count();
let start = if self.total_count < 256 { 0 } else { self.head };
AuditTraceIter {
trace: self,
pos: start,
remaining: count,
}
}
pub fn last(&self) -> Option<&AuditEvent> {
if self.total_count == 0 {
None
} else {
let idx = if self.head == 0 { 255 } else { self.head - 1 };
Some(&self.events[idx])
}
}
pub fn reset(&mut self) {
self.head = 0;
self.total_count = 0;
}
}
impl Default for AuditTrace {
fn default() -> Self {
Self::new()
}
}
pub struct AuditTraceIter<'a> {
trace: &'a AuditTrace,
pos: usize,
remaining: usize,
}
impl<'a> Iterator for AuditTraceIter<'a> {
type Item = &'a AuditEvent;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let event = &self.trace.events[self.pos];
self.pos = (self.pos + 1) % 256;
self.remaining -= 1;
Some(event)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'a> ExactSizeIterator for AuditTraceIter<'a> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_trace() {
let trace = AuditTrace::new();
assert_eq!(trace.total_count(), 0);
assert_eq!(trace.buffered_count(), 0);
assert!(trace.last().is_none());
}
#[test]
fn test_record_and_retrieve() {
let mut trace = AuditTrace::new();
trace.record(AuditEvent {
timestamp_ns: 1000,
residual: 0.5,
drift: 0.01,
slew: 0.001,
envelope_position: 0,
grammar_state: 0,
transition_occurred: false,
});
assert_eq!(trace.total_count(), 1);
assert_eq!(trace.buffered_count(), 1);
let last = trace.last().unwrap();
assert_eq!(last.timestamp_ns, 1000);
}
#[test]
fn test_ring_buffer_wraps() {
let mut trace = AuditTrace::new();
for i in 0..300u64 {
trace.record(AuditEvent {
timestamp_ns: i,
residual: i as f64,
drift: 0.0,
slew: 0.0,
envelope_position: 0,
grammar_state: 0,
transition_occurred: false,
});
}
assert_eq!(trace.total_count(), 300);
assert_eq!(trace.buffered_count(), 256);
assert_eq!(trace.last().unwrap().timestamp_ns, 299);
}
#[test]
fn test_iter_chronological() {
let mut trace = AuditTrace::new();
for i in 0..10u64 {
trace.record(AuditEvent {
timestamp_ns: i * 100,
residual: 0.0,
drift: 0.0,
slew: 0.0,
envelope_position: 0,
grammar_state: 0,
transition_occurred: false,
});
}
let timestamps: Vec<u64> = trace.iter().map(|e| e.timestamp_ns).collect();
assert_eq!(
timestamps,
vec![0, 100, 200, 300, 400, 500, 600, 700, 800, 900]
);
}
}