1use std::fmt;
12use std::sync::Mutex;
13use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
14
15use serde::{Deserialize, Serialize};
16
17#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub enum ImpulseType {
34 BeliefUpdate,
36 CoherenceAlert,
38 NoveltyDetected,
40 EdgeConfirmed,
42 EmbeddingRefined,
44 TurnClaim,
46 EndOfUtterance,
48 TurnShift,
50 Backchannel,
53 Custom(u8),
55}
56
57impl ImpulseType {
58 pub fn code(&self) -> u8 {
63 match self {
64 Self::BeliefUpdate => 0x01,
65 Self::CoherenceAlert => 0x02,
66 Self::NoveltyDetected => 0x03,
67 Self::EdgeConfirmed => 0x04,
68 Self::EmbeddingRefined => 0x05,
69 Self::TurnClaim => 0x50,
70 Self::EndOfUtterance => 0x51,
71 Self::TurnShift => 0x52,
72 Self::Backchannel => 0x60,
73 Self::Custom(c) => *c,
74 }
75 }
76}
77
78impl fmt::Display for ImpulseType {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::BeliefUpdate => write!(f, "BeliefUpdate"),
82 Self::CoherenceAlert => write!(f, "CoherenceAlert"),
83 Self::NoveltyDetected => write!(f, "NoveltyDetected"),
84 Self::EdgeConfirmed => write!(f, "EdgeConfirmed"),
85 Self::EmbeddingRefined => write!(f, "EmbeddingRefined"),
86 Self::TurnClaim => write!(f, "TurnClaim"),
87 Self::EndOfUtterance => write!(f, "EndOfUtterance"),
88 Self::TurnShift => write!(f, "TurnShift"),
89 Self::Backchannel => write!(f, "Backchannel"),
90 Self::Custom(code) => write!(f, "Custom({code})"),
91 }
92 }
93}
94
95pub struct Impulse {
105 pub id: u64,
107 pub source_structure: u8,
109 pub source_node: [u8; 32],
111 pub target_structure: u8,
113 pub impulse_type: ImpulseType,
115 pub payload: serde_json::Value,
117 pub hlc_timestamp: u64,
119 pub acknowledged: AtomicBool,
121}
122
123impl Clone for Impulse {
125 fn clone(&self) -> Self {
126 Self {
127 id: self.id,
128 source_structure: self.source_structure,
129 source_node: self.source_node,
130 target_structure: self.target_structure,
131 impulse_type: self.impulse_type.clone(),
132 payload: self.payload.clone(),
133 hlc_timestamp: self.hlc_timestamp,
134 acknowledged: AtomicBool::new(self.acknowledged.load(Ordering::Acquire)),
135 }
136 }
137}
138
139impl fmt::Debug for Impulse {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.debug_struct("Impulse")
142 .field("id", &self.id)
143 .field("source_structure", &self.source_structure)
144 .field("target_structure", &self.target_structure)
145 .field("impulse_type", &self.impulse_type)
146 .field("hlc_timestamp", &self.hlc_timestamp)
147 .field("acknowledged", &self.acknowledged.load(Ordering::Relaxed))
148 .finish()
149 }
150}
151
152pub struct ImpulseQueue {
158 queue: Mutex<Vec<Impulse>>,
159 next_id: AtomicU64,
160}
161
162impl ImpulseQueue {
163 pub fn new() -> Self {
165 Self {
166 queue: Mutex::new(Vec::new()),
167 next_id: AtomicU64::new(1),
168 }
169 }
170
171 pub fn emit(
173 &self,
174 source_structure: u8,
175 source_node: [u8; 32],
176 target_structure: u8,
177 impulse_type: ImpulseType,
178 payload: serde_json::Value,
179 hlc_timestamp: u64,
180 ) -> u64 {
181 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
182 let impulse = Impulse {
183 id,
184 source_structure,
185 source_node,
186 target_structure,
187 impulse_type,
188 payload,
189 hlc_timestamp,
190 acknowledged: AtomicBool::new(false),
191 };
192 let mut q = self.queue.lock().expect("impulse queue poisoned");
193 q.push(impulse);
194 id
195 }
196
197 pub fn drain_ready(&self) -> Vec<Impulse> {
200 let mut q = self.queue.lock().expect("impulse queue poisoned");
201 let drained: Vec<Impulse> = q
202 .drain(..)
203 .filter(|imp| !imp.acknowledged.load(Ordering::Acquire))
204 .collect();
205 let mut sorted = drained;
206 sorted.sort_by_key(|imp| imp.hlc_timestamp);
207 sorted
208 }
209
210 pub fn drain_ready_limited(&self, max: usize) -> Vec<Impulse> {
218 let mut q = self.queue.lock().expect("impulse queue poisoned");
219 let mut ready: Vec<Impulse> = std::mem::take(&mut *q)
220 .into_iter()
221 .filter(|imp| !imp.acknowledged.load(Ordering::Acquire))
222 .collect();
223 ready.sort_by_key(|imp| imp.hlc_timestamp);
224 if ready.len() > max {
225 *q = ready.split_off(max);
228 }
229 ready
230 }
231
232 pub fn len(&self) -> usize {
234 self.queue.lock().expect("impulse queue poisoned").len()
235 }
236
237 pub fn is_empty(&self) -> bool {
239 self.len() == 0
240 }
241
242 pub fn clear(&self) {
244 self.queue.lock().expect("impulse queue poisoned").clear();
245 }
246
247 pub fn pending_count(&self) -> usize {
249 self.queue
250 .lock()
251 .expect("impulse queue poisoned")
252 .iter()
253 .filter(|imp| !imp.acknowledged.load(Ordering::Acquire))
254 .count()
255 }
256}
257
258impl Default for ImpulseQueue {
259 fn default() -> Self {
260 Self::new()
261 }
262}
263
264#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn impulse_type_display() {
274 assert_eq!(ImpulseType::BeliefUpdate.to_string(), "BeliefUpdate");
275 assert_eq!(ImpulseType::CoherenceAlert.to_string(), "CoherenceAlert");
276 assert_eq!(ImpulseType::NoveltyDetected.to_string(), "NoveltyDetected");
277 assert_eq!(ImpulseType::EdgeConfirmed.to_string(), "EdgeConfirmed");
278 assert_eq!(
279 ImpulseType::EmbeddingRefined.to_string(),
280 "EmbeddingRefined"
281 );
282 assert_eq!(ImpulseType::Custom(42).to_string(), "Custom(42)");
283 }
284
285 #[test]
286 fn impulse_queue_new_empty() {
287 let q = ImpulseQueue::new();
288 assert!(q.is_empty());
289 assert_eq!(q.len(), 0);
290 assert_eq!(q.pending_count(), 0);
291 }
292
293 #[test]
294 fn impulse_queue_emit_assigns_id() {
295 let q = ImpulseQueue::new();
296 let node = [0u8; 32];
297 let id1 = q.emit(
298 0,
299 node,
300 2,
301 ImpulseType::BeliefUpdate,
302 serde_json::json!({}),
303 100,
304 );
305 let id2 = q.emit(
306 1,
307 node,
308 0,
309 ImpulseType::CoherenceAlert,
310 serde_json::json!({}),
311 200,
312 );
313 assert_eq!(id1, 1);
314 assert_eq!(id2, 2);
315 assert_eq!(q.len(), 2);
316 }
317
318 #[test]
319 fn impulse_queue_drain_sorted_by_hlc() {
320 let q = ImpulseQueue::new();
321 let node = [0u8; 32];
322 q.emit(
324 0,
325 node,
326 2,
327 ImpulseType::BeliefUpdate,
328 serde_json::json!({}),
329 300,
330 );
331 q.emit(
332 1,
333 node,
334 0,
335 ImpulseType::CoherenceAlert,
336 serde_json::json!({}),
337 100,
338 );
339 q.emit(
340 2,
341 node,
342 0,
343 ImpulseType::NoveltyDetected,
344 serde_json::json!({}),
345 200,
346 );
347
348 let drained = q.drain_ready();
349 assert_eq!(drained.len(), 3);
350 assert_eq!(drained[0].hlc_timestamp, 100);
351 assert_eq!(drained[1].hlc_timestamp, 200);
352 assert_eq!(drained[2].hlc_timestamp, 300);
353 }
354
355 #[test]
356 fn impulse_queue_drain_removes_items() {
357 let q = ImpulseQueue::new();
358 let node = [0u8; 32];
359 q.emit(
360 0,
361 node,
362 2,
363 ImpulseType::BeliefUpdate,
364 serde_json::json!({}),
365 1,
366 );
367 q.emit(
368 0,
369 node,
370 2,
371 ImpulseType::EdgeConfirmed,
372 serde_json::json!({}),
373 2,
374 );
375 assert_eq!(q.len(), 2);
376
377 let drained = q.drain_ready();
378 assert_eq!(drained.len(), 2);
379 assert!(q.is_empty());
380 }
381
382 #[test]
383 fn impulse_queue_clear() {
384 let q = ImpulseQueue::new();
385 let node = [0u8; 32];
386 q.emit(
387 0,
388 node,
389 1,
390 ImpulseType::EmbeddingRefined,
391 serde_json::json!({}),
392 10,
393 );
394 q.emit(
395 0,
396 node,
397 1,
398 ImpulseType::Custom(7),
399 serde_json::json!({}),
400 20,
401 );
402 assert_eq!(q.len(), 2);
403
404 q.clear();
405 assert!(q.is_empty());
406 assert_eq!(q.len(), 0);
407 }
408
409 #[test]
410 fn impulse_queue_pending_count() {
411 let q = ImpulseQueue::new();
412 let node = [0u8; 32];
413 q.emit(
414 0,
415 node,
416 2,
417 ImpulseType::BeliefUpdate,
418 serde_json::json!({}),
419 1,
420 );
421 q.emit(
422 1,
423 node,
424 0,
425 ImpulseType::CoherenceAlert,
426 serde_json::json!({}),
427 2,
428 );
429 assert_eq!(q.pending_count(), 2);
430
431 {
433 let guard = q.queue.lock().unwrap();
434 guard[0].acknowledged.store(true, Ordering::Release);
435 }
436 assert_eq!(q.pending_count(), 1);
437 }
438
439 #[test]
440 fn turn_impulse_codes_and_display() {
441 assert_eq!(ImpulseType::TurnClaim.code(), 0x50);
443 assert_eq!(ImpulseType::EndOfUtterance.code(), 0x51);
444 assert_eq!(ImpulseType::TurnShift.code(), 0x52);
445 assert_eq!(ImpulseType::Backchannel.code(), 0x60);
446 assert_eq!(ImpulseType::Custom(0x77).code(), 0x77);
447
448 assert_eq!(ImpulseType::TurnClaim.to_string(), "TurnClaim");
449 assert_eq!(ImpulseType::EndOfUtterance.to_string(), "EndOfUtterance");
450 assert_eq!(ImpulseType::TurnShift.to_string(), "TurnShift");
451 assert_eq!(ImpulseType::Backchannel.to_string(), "Backchannel");
452 }
453
454 #[test]
455 fn turn_impulse_serde_roundtrip_by_name() {
456 let json = serde_json::to_string(&ImpulseType::EndOfUtterance).unwrap();
458 assert_eq!(json, "\"EndOfUtterance\"");
459 let back: ImpulseType = serde_json::from_str(&json).unwrap();
460 assert_eq!(back, ImpulseType::EndOfUtterance);
461 let custom = serde_json::to_string(&ImpulseType::Custom(7)).unwrap();
463 assert_eq!(custom, "{\"Custom\":7}");
464 }
465
466 #[test]
467 fn turn_impulses_drain_hlc_ordered() {
468 let q = ImpulseQueue::new();
471 let speaker = [9u8; 32];
472 q.emit(
474 0,
475 speaker,
476 0,
477 ImpulseType::EndOfUtterance,
478 json_payload(),
479 300,
480 );
481 q.emit(0, speaker, 0, ImpulseType::Backchannel, json_payload(), 100);
482 q.emit(0, speaker, 0, ImpulseType::TurnClaim, json_payload(), 200);
483
484 let drained = q.drain_ready();
485 assert_eq!(drained.len(), 3);
486 assert_eq!(drained[0].impulse_type, ImpulseType::Backchannel);
488 assert_eq!(drained[0].hlc_timestamp, 100);
489 assert_eq!(drained[1].impulse_type, ImpulseType::TurnClaim);
490 assert_eq!(drained[1].hlc_timestamp, 200);
491 assert_eq!(drained[2].impulse_type, ImpulseType::EndOfUtterance);
492 assert_eq!(drained[2].hlc_timestamp, 300);
493 }
494
495 fn json_payload() -> serde_json::Value {
496 serde_json::json!({})
497 }
498
499 #[test]
500 fn impulse_emit_and_acknowledge() {
501 let q = ImpulseQueue::new();
502 let node = [1u8; 32];
503 q.emit(
504 0,
505 node,
506 2,
507 ImpulseType::NoveltyDetected,
508 serde_json::json!({"k": "v"}),
509 50,
510 );
511 q.emit(
512 0,
513 node,
514 3,
515 ImpulseType::EdgeConfirmed,
516 serde_json::json!(null),
517 60,
518 );
519
520 let drained = q.drain_ready();
521 assert_eq!(drained.len(), 2);
522 assert!(q.is_empty());
524
525 for imp in &drained {
527 imp.acknowledged.store(true, Ordering::Release);
528 }
529
530 for imp in &drained {
532 assert!(imp.acknowledged.load(Ordering::Acquire));
533 }
534 }
535}