//! Discrete-event engine. //! //! Single-threaded virtual time `f64` seconds. Events are stored in a min-heap //! keyed by `(time, seq)` so equal-time events fire in insertion order. use std::cmp::Ordering; use std::collections::BinaryHeap; use super::events::Event; #[derive(Debug)] struct Slot { time: f64, seq: u64, event: Event, } impl Eq for Slot {} impl PartialEq for Slot { fn eq(&self, other: &Self) -> bool { self.time == other.time && self.seq == other.seq } } impl Ord for Slot { fn cmp(&self, other: &Self) -> Ordering { // Reverse so BinaryHeap acts as a min-heap. other .time .partial_cmp(&self.time) .unwrap_or(Ordering::Equal) .then_with(|| other.seq.cmp(&self.seq)) } } impl PartialOrd for Slot { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[derive(Debug, Default)] pub struct EventQueue { heap: BinaryHeap, seq: u64, now: f64, } impl EventQueue { pub fn new() -> Self { Self::default() } pub fn now(&self) -> f64 { self.now } pub fn schedule(&mut self, time: f64, event: Event) { let t = time.max(self.now); self.seq += 1; self.heap.push(Slot { time: t, seq: self.seq, event, }); } pub fn pop(&mut self) -> Option<(f64, Event)> { let slot = self.heap.pop()?; if slot.time > self.now { self.now = slot.time; } Some((slot.time, slot.event)) } pub fn len(&self) -> usize { self.heap.len() } pub fn is_empty(&self) -> bool { self.heap.is_empty() } } #[cfg(test)] mod tests { use super::*; use crate::types::InstanceId; #[test] fn pops_in_time_order() { let mut q = EventQueue::new(); q.schedule( 2.0, Event::BatchTick { bucket: 0, instance: 0 as InstanceId, }, ); q.schedule( 1.0, Event::BatchTick { bucket: 0, instance: 1, }, ); q.schedule( 1.5, Event::BatchTick { bucket: 0, instance: 2, }, ); let (t1, _) = q.pop().unwrap(); let (t2, _) = q.pop().unwrap(); let (t3, _) = q.pop().unwrap(); assert!(t1 <= t2 && t2 <= t3); assert!((t1 - 1.0).abs() < 1e-12); assert!((t3 - 2.0).abs() < 1e-12); } #[test] fn equal_time_fifo() { let mut q = EventQueue::new(); q.schedule( 1.0, Event::BatchTick { bucket: 0, instance: 7, }, ); q.schedule( 1.0, Event::BatchTick { bucket: 1, instance: 8, }, ); let (_, e1) = q.pop().unwrap(); let (_, e2) = q.pop().unwrap(); match (e1, e2) { (Event::BatchTick { instance: a, .. }, Event::BatchTick { instance: b, .. }) => { assert_eq!(a, 7); assert_eq!(b, 8); } _ => panic!("wrong events"), } } }