use std::collections::HashMap;
use std::any::TypeId;
use std::rc::Rc;
type UntypedCallback = Box<dyn FnMut(*const ())>;
pub struct Dispatcher {
map: HashMap<TypeId, Vec<UntypedCallback>>,
}
impl Dispatcher {
pub fn new() -> Self {
Self {
map: HashMap::new()
}
}
pub fn register<T: 'static>(&mut self)
{
self.map.entry(TypeId::of::<T>()).or_default();
}
pub fn subscribe<F, T>(&mut self, mut f: F)
where
F: FnMut(Rc<T>) + 'static,
T: 'static
{
let mut subs = match self.map.get_mut(&TypeId::of::<T>()) {
None => panic!("Can't subscribe to a message which is not registered!"),
Some(subs) => subs
};
let mut wrapped = move |msg: *const ()| {
let typed = unsafe { Rc::from_raw(msg as *const T) };
(f)(typed)
};
self.map.get_mut(&TypeId::of::<T>()).unwrap().push(Box::new(wrapped));
}
pub fn dispatch<T: 'static>(&mut self, msg: Rc<T>) {
let mut subscribers = self.map.get_mut(&TypeId::of::<T>())
.expect("Can not dispatch a message which has not been registered");
for subscriber in subscribers.iter_mut() {
let untyped = unsafe { Rc::into_raw(Rc::clone(&msg)) };
(subscriber)(untyped as *const ());
}
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use std::cell::RefCell;
use super::{Dispatcher};
struct Empty {}
struct Greeting {
greeting: String
}
struct Farawell {
farawell: String
}
struct Context {
answer: i32,
called: bool,
}
fn pop(msg: Rc<Greeting>) {
println!("Greeting {}", msg.greeting);
assert_eq!(&msg.greeting, "Hello, World!");
}
#[test]
fn test_basic()
{
let mut dispatcher = Dispatcher::new();
dispatcher.register::<Greeting>();
let called = Rc::new(RefCell::new(false));
{
let called = called.clone();
let mut pop = move |msg| {
*called.borrow_mut() = true;
pop(msg);
};
dispatcher.subscribe(pop);
}
let message = Greeting {
greeting: "Hello, World!".to_string()
};
dispatcher.dispatch(Rc::new(message));
assert_eq!(*called.borrow(), true);
}
#[test]
fn test_multiple_subscribers()
{
let mut dispatcher = Dispatcher::new();
dispatcher.register::<Greeting>();
let counter = Rc::new(RefCell::new(0));
for _ in 0..2 {
let counter = Rc::clone(&counter);
dispatcher.subscribe(move |msg: Rc<Greeting>| {
assert_eq!(msg.greeting, "Hello, World!");
*counter.borrow_mut() += 1;
});
}
dispatcher.dispatch(Rc::new(Greeting { greeting: "Hello, World!".to_string() }));
assert_eq!(*counter.borrow(), 2);
}
}