use crate::event::Event;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
pub type HandlerFn = Arc<dyn Fn(&Event) + Send + Sync>;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct HandlerId(pub(crate) u64);
struct Registered {
id: HandlerId,
f: HandlerFn,
}
#[derive(Default)]
struct Table {
typed: HashMap<String, Vec<Registered>>,
general: Vec<Registered>,
}
#[derive(Default)]
pub struct HandlerRegistry {
table: RwLock<Table>,
next_id: AtomicU64,
}
impl HandlerRegistry {
pub fn register(&self, event_type: impl Into<String>, f: HandlerFn) -> HandlerId {
let id = HandlerId(self.next_id.fetch_add(1, Ordering::Relaxed));
let mut t = self.table.write().unwrap();
t.typed.entry(event_type.into()).or_default().push(Registered { id, f });
id
}
pub fn unregister(&self, id: HandlerId) -> bool {
let mut t = self.table.write().unwrap();
let mut removed = false;
for (_, list) in t.typed.iter_mut() {
if let Some(pos) = list.iter().position(|r| r.id == id) {
list.remove(pos);
removed = true;
}
}
t.typed.retain(|_, l| !l.is_empty());
if !removed {
let before = t.general.len();
t.general.retain(|r| r.id != id);
removed = before != t.general.len();
}
removed
}
pub fn register_general(&self, f: HandlerFn) -> HandlerId {
let id = HandlerId(self.next_id.fetch_add(1, Ordering::Relaxed));
self.table.write().unwrap().general.push(Registered { id, f });
id
}
pub fn unregister_general(&self, id: HandlerId) -> bool {
let mut t = self.table.write().unwrap();
let before = t.general.len();
t.general.retain(|r| r.id != id);
before != t.general.len()
}
pub(crate) fn snapshot(&self, event_type: &str) -> Vec<HandlerFn> {
let t = self.table.read().unwrap();
let mut out = Vec::new();
if let Some(list) = t.typed.get(event_type) {
out.extend(list.iter().map(|r| r.f.clone()));
}
out.extend(t.general.iter().map(|r| r.f.clone()));
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
#[test]
fn dispatch_and_unregister() {
let reg = HandlerRegistry::default();
let cnt = Arc::new(AtomicUsize::new(0));
let c1 = cnt.clone();
let id1 = reg.register("eTick.", Arc::new(move |_| {
c1.fetch_add(1, Ordering::SeqCst);
}));
let c2 = cnt.clone();
let id2 = reg.register("eTick.", Arc::new(move |_| {
c2.fetch_add(1, Ordering::SeqCst);
}));
let c3 = cnt.clone();
let g = reg.register_general(Arc::new(move |_| {
c3.fetch_add(10, Ordering::SeqCst);
}));
let snap = reg.snapshot("eTick.");
assert_eq!(snap.len(), 3, "2 typed + 1 general");
assert_eq!(cnt.load(Ordering::SeqCst), 0);
assert!(reg.unregister(g));
assert!(!reg.unregister(g));
let snap = reg.snapshot("eTick.");
assert_eq!(snap.len(), 2, "general withdrawn");
assert!(reg.unregister(id1));
assert!(reg.unregister(id2));
let snap = reg.snapshot("eTick.");
assert!(snap.is_empty(), "empty handler list should be recycled");
let c4 = cnt.clone();
let id_re = reg.register("eTick.", Arc::new(move |_| {
c4.fetch_add(1, Ordering::SeqCst);
}));
assert_eq!(reg.snapshot("eTick.").len(), 1, "re-register rebuilds list");
assert!(reg.unregister(id_re));
}
}