1#[derive(Debug, Clone, Copy)]
12pub struct AuditEvent {
13 pub timestamp_ns: u64,
15 pub residual: f64,
17 pub drift: f64,
19 pub slew: f64,
21 pub envelope_position: u8, pub grammar_state: u8, pub transition_occurred: bool,
27}
28
29pub struct AuditTrace {
35 events: [AuditEvent; 256],
37 head: usize,
39 total_count: u64,
41}
42
43impl AuditTrace {
44 pub fn new() -> Self {
46 Self {
47 events: [AuditEvent {
48 timestamp_ns: 0,
49 residual: 0.0,
50 drift: 0.0,
51 slew: 0.0,
52 envelope_position: 0,
53 grammar_state: 0,
54 transition_occurred: false,
55 }; 256],
56 head: 0,
57 total_count: 0,
58 }
59 }
60
61 pub fn record(&mut self, event: AuditEvent) {
63 self.events[self.head] = event;
64 self.head = (self.head + 1) % 256;
65 self.total_count += 1;
66 }
67
68 pub fn total_count(&self) -> u64 {
70 self.total_count
71 }
72
73 pub fn buffered_count(&self) -> usize {
75 if self.total_count < 256 {
76 self.total_count as usize
77 } else {
78 256
79 }
80 }
81
82 pub fn iter(&self) -> AuditTraceIter<'_> {
84 let count = self.buffered_count();
85 let start = if self.total_count < 256 { 0 } else { self.head };
86 AuditTraceIter {
87 trace: self,
88 pos: start,
89 remaining: count,
90 }
91 }
92
93 pub fn last(&self) -> Option<&AuditEvent> {
95 if self.total_count == 0 {
96 None
97 } else {
98 let idx = if self.head == 0 { 255 } else { self.head - 1 };
99 Some(&self.events[idx])
100 }
101 }
102
103 pub fn reset(&mut self) {
105 self.head = 0;
106 self.total_count = 0;
107 }
108}
109
110impl Default for AuditTrace {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116pub struct AuditTraceIter<'a> {
118 trace: &'a AuditTrace,
119 pos: usize,
120 remaining: usize,
121}
122
123impl<'a> Iterator for AuditTraceIter<'a> {
124 type Item = &'a AuditEvent;
125
126 fn next(&mut self) -> Option<Self::Item> {
127 if self.remaining == 0 {
128 return None;
129 }
130 let event = &self.trace.events[self.pos];
131 self.pos = (self.pos + 1) % 256;
132 self.remaining -= 1;
133 Some(event)
134 }
135
136 fn size_hint(&self) -> (usize, Option<usize>) {
137 (self.remaining, Some(self.remaining))
138 }
139}
140
141impl<'a> ExactSizeIterator for AuditTraceIter<'a> {}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_empty_trace() {
149 let trace = AuditTrace::new();
150 assert_eq!(trace.total_count(), 0);
151 assert_eq!(trace.buffered_count(), 0);
152 assert!(trace.last().is_none());
153 }
154
155 #[test]
156 fn test_record_and_retrieve() {
157 let mut trace = AuditTrace::new();
158 trace.record(AuditEvent {
159 timestamp_ns: 1000,
160 residual: 0.5,
161 drift: 0.01,
162 slew: 0.001,
163 envelope_position: 0,
164 grammar_state: 0,
165 transition_occurred: false,
166 });
167 assert_eq!(trace.total_count(), 1);
168 assert_eq!(trace.buffered_count(), 1);
169 let last = trace.last().unwrap();
170 assert_eq!(last.timestamp_ns, 1000);
171 }
172
173 #[test]
174 fn test_ring_buffer_wraps() {
175 let mut trace = AuditTrace::new();
176 for i in 0..300u64 {
177 trace.record(AuditEvent {
178 timestamp_ns: i,
179 residual: i as f64,
180 drift: 0.0,
181 slew: 0.0,
182 envelope_position: 0,
183 grammar_state: 0,
184 transition_occurred: false,
185 });
186 }
187 assert_eq!(trace.total_count(), 300);
188 assert_eq!(trace.buffered_count(), 256);
189 assert_eq!(trace.last().unwrap().timestamp_ns, 299);
191 }
192
193 #[test]
194 fn test_iter_chronological() {
195 let mut trace = AuditTrace::new();
196 for i in 0..10u64 {
197 trace.record(AuditEvent {
198 timestamp_ns: i * 100,
199 residual: 0.0,
200 drift: 0.0,
201 slew: 0.0,
202 envelope_position: 0,
203 grammar_state: 0,
204 transition_occurred: false,
205 });
206 }
207 let timestamps: Vec<u64> = trace.iter().map(|e| e.timestamp_ns).collect();
208 assert_eq!(
209 timestamps,
210 vec![0, 100, 200, 300, 400, 500, 600, 700, 800, 900]
211 );
212 }
213}