pub mod event;
pub mod handler;
pub mod engine;
pub use engine::{EventEngine, PutError};
pub use event::{Event, EventData};
pub use handler::HandlerFn;
pub use handler::HandlerId;
pub use handler::HandlerRegistry;
pub const EVENT_TIMER: &str = "eTimer";
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
fn wait_for(cond: impl Fn() -> bool) -> bool {
for _ in 0..200 {
if cond() {
return true;
}
std::thread::sleep(Duration::from_millis(10));
}
cond()
}
#[test]
fn dispatch_order_type_then_general() {
let engine = EventEngine::new();
engine.start();
let seq: Arc<spin_free_seq::Seq> = Arc::default();
let s1 = seq.clone();
engine.register("eTick.", Arc::new(move |_| s1.record(0)));
let s2 = seq.clone();
engine.register_general(Arc::new(move |_| s2.record(1)));
engine.put(Event::new("eTick.")).unwrap();
for _ in 0..50 {
if seq.is(0, 1) {
break;
}
std::thread::sleep(Duration::from_millis(10));
}
assert_eq!(seq.0.load(Ordering::SeqCst) & 3, 3);
engine.stop();
}
#[test]
fn timer_event_fires() {
let engine = EventEngine::with_interval(Duration::from_millis(50));
engine.start();
let cnt = Arc::new(AtomicUsize::new(0));
let c2 = cnt.clone();
engine.register(EVENT_TIMER, Arc::new(move |_| {
c2.fetch_add(1, Ordering::SeqCst);
}));
std::thread::sleep(Duration::from_millis(400));
let n = cnt.load(Ordering::SeqCst);
assert!(n >= 3, "timer fired too few times: {n}");
engine.stop();
}
#[test]
fn duplicate_registration_yields_unique_ids() {
let engine = EventEngine::new(); let bump = Arc::new(AtomicUsize::new(0));
let b1 = bump.clone();
let id1 = engine.register("eTick.", Arc::new(move |_| {
b1.fetch_add(1, Ordering::SeqCst);
}));
let b2 = bump.clone();
let id2 = engine.register("eTick.", Arc::new(move |_| {
b2.fetch_add(1, Ordering::SeqCst);
}));
assert_ne!(id1, id2, "each registration must get a unique HandlerId");
assert!(engine.unregister(id1));
assert!(engine.unregister(id2));
assert!(!engine.unregister(id1), "double unregister must be false");
}
#[test]
fn handler_panic_does_not_kill_dispatcher() {
let engine = EventEngine::new();
engine.start();
let cnt = Arc::new(AtomicUsize::new(0));
let _ = engine.register("eTick.", Arc::new(move |_| { panic!("boom"); }));
let c2 = cnt.clone();
engine.register("eTick.", Arc::new(move |_| {
c2.fetch_add(1, Ordering::SeqCst);
}));
engine.put(Event::new("eTick.")).unwrap();
assert!(
wait_for(|| cnt.load(Ordering::SeqCst) >= 1),
"handler after a panicking one must still run"
);
engine.put(Event::new("eTick.")).unwrap();
assert!(
wait_for(|| cnt.load(Ordering::SeqCst) >= 2),
"events dispatched after a handler panic must still be processed"
);
engine.stop();
}
#[test]
fn put_after_stop_fails_and_stop_is_idempotent() {
let engine = EventEngine::new();
engine.start();
engine.stop();
assert!(!engine.is_active());
assert_eq!(
engine.put(Event::new("eTick.")),
Err(PutError::Stopped)
);
engine.stop(); engine.stop(); assert_eq!(engine.pending_events(), 0, "stop must reset pending count");
}
#[test]
fn same_type_events_delivered_in_put_order() {
let engine = EventEngine::new();
engine.start();
let seen: Arc<Mutex<Vec<usize>>> = Arc::default();
let s = seen.clone();
let _ = engine.register("eTick.", Arc::new(move |e| {
let v = *e.data.downcast_ref::<usize>().expect("usize payload");
s.lock().unwrap().push(v);
}));
const N: usize = 200;
for i in 0..N {
engine.put(Event::with_data("eTick.", Arc::new(i))).unwrap();
}
assert!(
wait_for(|| seen.lock().unwrap().len() >= N),
"all events must be delivered"
);
let expected: Vec<usize> = (0..N).collect();
assert_eq!(
*seen.lock().unwrap(),
expected,
"delivery must preserve put order"
);
engine.stop();
}
#[test]
fn event_engine_is_sync_and_concurrent_put_works() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<EventEngine>();
let engine = Arc::new(EventEngine::new());
engine.start(); let cnt = Arc::new(AtomicUsize::new(0));
let c = cnt.clone();
engine.register("eTick.", Arc::new(move |_| {
c.fetch_add(1, Ordering::SeqCst);
}));
let mut handles = Vec::new();
for _ in 0..4 {
let e = engine.clone();
handles.push(std::thread::spawn(move || {
for _ in 0..50 {
e.put(Event::new("eTick.")).unwrap();
}
}));
}
for h in handles {
h.join().unwrap();
}
const N: usize = 200;
assert!(
wait_for(|| cnt.load(Ordering::SeqCst) >= N),
"all cross-thread events must be delivered"
);
engine.stop();
}
mod spin_free_seq {
use std::sync::atomic::{AtomicI64, Ordering};
pub struct Seq(pub AtomicI64);
impl Seq {
pub fn record(&self, i: i64) {
let _ = self.0.fetch_or(1 << i, Ordering::SeqCst);
}
pub fn is(&self, a: i64, b: i64) -> bool {
self.0.load(Ordering::SeqCst) == (1 << a) + (1 << b)
}
}
impl Default for Seq {
fn default() -> Self {
Self(AtomicI64::new(0))
}
}
}
}