use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;
use serde::{Deserialize, Serialize};
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ImpulseType {
BeliefUpdate,
CoherenceAlert,
NoveltyDetected,
EdgeConfirmed,
EmbeddingRefined,
Custom(u8),
}
impl fmt::Display for ImpulseType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BeliefUpdate => write!(f, "BeliefUpdate"),
Self::CoherenceAlert => write!(f, "CoherenceAlert"),
Self::NoveltyDetected => write!(f, "NoveltyDetected"),
Self::EdgeConfirmed => write!(f, "EdgeConfirmed"),
Self::EmbeddingRefined => write!(f, "EmbeddingRefined"),
Self::Custom(code) => write!(f, "Custom({code})"),
}
}
}
pub struct Impulse {
pub id: u64,
pub source_structure: u8,
pub source_node: [u8; 32],
pub target_structure: u8,
pub impulse_type: ImpulseType,
pub payload: serde_json::Value,
pub hlc_timestamp: u64,
pub acknowledged: AtomicBool,
}
impl Clone for Impulse {
fn clone(&self) -> Self {
Self {
id: self.id,
source_structure: self.source_structure,
source_node: self.source_node,
target_structure: self.target_structure,
impulse_type: self.impulse_type.clone(),
payload: self.payload.clone(),
hlc_timestamp: self.hlc_timestamp,
acknowledged: AtomicBool::new(self.acknowledged.load(Ordering::Acquire)),
}
}
}
impl fmt::Debug for Impulse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Impulse")
.field("id", &self.id)
.field("source_structure", &self.source_structure)
.field("target_structure", &self.target_structure)
.field("impulse_type", &self.impulse_type)
.field("hlc_timestamp", &self.hlc_timestamp)
.field("acknowledged", &self.acknowledged.load(Ordering::Relaxed))
.finish()
}
}
pub struct ImpulseQueue {
queue: Mutex<Vec<Impulse>>,
next_id: AtomicU64,
}
impl ImpulseQueue {
pub fn new() -> Self {
Self {
queue: Mutex::new(Vec::new()),
next_id: AtomicU64::new(1),
}
}
pub fn emit(
&self,
source_structure: u8,
source_node: [u8; 32],
target_structure: u8,
impulse_type: ImpulseType,
payload: serde_json::Value,
hlc_timestamp: u64,
) -> u64 {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let impulse = Impulse {
id,
source_structure,
source_node,
target_structure,
impulse_type,
payload,
hlc_timestamp,
acknowledged: AtomicBool::new(false),
};
let mut q = self.queue.lock().expect("impulse queue poisoned");
q.push(impulse);
id
}
pub fn drain_ready(&self) -> Vec<Impulse> {
let mut q = self.queue.lock().expect("impulse queue poisoned");
let drained: Vec<Impulse> = q
.drain(..)
.filter(|imp| !imp.acknowledged.load(Ordering::Acquire))
.collect();
let mut sorted = drained;
sorted.sort_by_key(|imp| imp.hlc_timestamp);
sorted
}
pub fn len(&self) -> usize {
self.queue.lock().expect("impulse queue poisoned").len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
self.queue.lock().expect("impulse queue poisoned").clear();
}
pub fn pending_count(&self) -> usize {
self.queue
.lock()
.expect("impulse queue poisoned")
.iter()
.filter(|imp| !imp.acknowledged.load(Ordering::Acquire))
.count()
}
}
impl Default for ImpulseQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn impulse_type_display() {
assert_eq!(ImpulseType::BeliefUpdate.to_string(), "BeliefUpdate");
assert_eq!(ImpulseType::CoherenceAlert.to_string(), "CoherenceAlert");
assert_eq!(ImpulseType::NoveltyDetected.to_string(), "NoveltyDetected");
assert_eq!(ImpulseType::EdgeConfirmed.to_string(), "EdgeConfirmed");
assert_eq!(
ImpulseType::EmbeddingRefined.to_string(),
"EmbeddingRefined"
);
assert_eq!(ImpulseType::Custom(42).to_string(), "Custom(42)");
}
#[test]
fn impulse_queue_new_empty() {
let q = ImpulseQueue::new();
assert!(q.is_empty());
assert_eq!(q.len(), 0);
assert_eq!(q.pending_count(), 0);
}
#[test]
fn impulse_queue_emit_assigns_id() {
let q = ImpulseQueue::new();
let node = [0u8; 32];
let id1 = q.emit(0, node, 2, ImpulseType::BeliefUpdate, serde_json::json!({}), 100);
let id2 = q.emit(1, node, 0, ImpulseType::CoherenceAlert, serde_json::json!({}), 200);
assert_eq!(id1, 1);
assert_eq!(id2, 2);
assert_eq!(q.len(), 2);
}
#[test]
fn impulse_queue_drain_sorted_by_hlc() {
let q = ImpulseQueue::new();
let node = [0u8; 32];
q.emit(0, node, 2, ImpulseType::BeliefUpdate, serde_json::json!({}), 300);
q.emit(1, node, 0, ImpulseType::CoherenceAlert, serde_json::json!({}), 100);
q.emit(2, node, 0, ImpulseType::NoveltyDetected, serde_json::json!({}), 200);
let drained = q.drain_ready();
assert_eq!(drained.len(), 3);
assert_eq!(drained[0].hlc_timestamp, 100);
assert_eq!(drained[1].hlc_timestamp, 200);
assert_eq!(drained[2].hlc_timestamp, 300);
}
#[test]
fn impulse_queue_drain_removes_items() {
let q = ImpulseQueue::new();
let node = [0u8; 32];
q.emit(0, node, 2, ImpulseType::BeliefUpdate, serde_json::json!({}), 1);
q.emit(0, node, 2, ImpulseType::EdgeConfirmed, serde_json::json!({}), 2);
assert_eq!(q.len(), 2);
let drained = q.drain_ready();
assert_eq!(drained.len(), 2);
assert!(q.is_empty());
}
#[test]
fn impulse_queue_clear() {
let q = ImpulseQueue::new();
let node = [0u8; 32];
q.emit(0, node, 1, ImpulseType::EmbeddingRefined, serde_json::json!({}), 10);
q.emit(0, node, 1, ImpulseType::Custom(7), serde_json::json!({}), 20);
assert_eq!(q.len(), 2);
q.clear();
assert!(q.is_empty());
assert_eq!(q.len(), 0);
}
#[test]
fn impulse_queue_pending_count() {
let q = ImpulseQueue::new();
let node = [0u8; 32];
q.emit(0, node, 2, ImpulseType::BeliefUpdate, serde_json::json!({}), 1);
q.emit(1, node, 0, ImpulseType::CoherenceAlert, serde_json::json!({}), 2);
assert_eq!(q.pending_count(), 2);
{
let guard = q.queue.lock().unwrap();
guard[0].acknowledged.store(true, Ordering::Release);
}
assert_eq!(q.pending_count(), 1);
}
#[test]
fn impulse_emit_and_acknowledge() {
let q = ImpulseQueue::new();
let node = [1u8; 32];
q.emit(0, node, 2, ImpulseType::NoveltyDetected, serde_json::json!({"k": "v"}), 50);
q.emit(0, node, 3, ImpulseType::EdgeConfirmed, serde_json::json!(null), 60);
let drained = q.drain_ready();
assert_eq!(drained.len(), 2);
assert!(q.is_empty());
for imp in &drained {
imp.acknowledged.store(true, Ordering::Release);
}
for imp in &drained {
assert!(imp.acknowledged.load(Ordering::Acquire));
}
}
}