#[cfg(test)]
mod tests;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
type Subscriber = Arc<dyn Fn(&dyn Any) + Send + Sync>;
type Subscribers = HashMap<TypeId, Vec<(SubscriptionId, Subscriber)>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SubscriptionId(u64);
#[derive(Clone, Default)]
pub struct EventBus {
subscribers: Arc<RwLock<Subscribers>>,
next_id: Arc<AtomicU64>,
}
impl EventBus {
pub fn new() -> Self {
EventBus::default()
}
pub fn subscribe<E, F>(&self, handler: F) -> SubscriptionId
where
E: Any,
F: Fn(&E) + Send + Sync + 'static,
{
let id = SubscriptionId(self.next_id.fetch_add(1, Ordering::Relaxed));
let subscriber: Subscriber = Arc::new(move |event: &dyn Any| {
if let Some(event) = event.downcast_ref::<E>() {
handler(event);
}
});
self.subscribers.write().unwrap_or_else(|e| e.into_inner()).entry(TypeId::of::<E>()).or_default().push((id, subscriber));
id
}
pub fn unsubscribe(&self, id: SubscriptionId) -> bool {
let mut subscribers = self.subscribers.write().unwrap_or_else(|e| e.into_inner());
for list in subscribers.values_mut() {
if let Some(position) = list.iter().position(|(i, _)| *i == id) {
list.remove(position);
return true;
}
}
false
}
pub fn publish<E: Any>(&self, event: &E) -> usize {
let subscribers: Vec<Subscriber> = match self.subscribers.read().unwrap_or_else(|e| e.into_inner()).get(&TypeId::of::<E>()) {
Some(list) => list.iter().map(|(_, s)| Arc::clone(s)).collect(),
None => return 0,
};
let mut completed = 0;
for subscriber in subscribers {
match catch_unwind(AssertUnwindSafe(|| subscriber(event))) {
Ok(()) => completed += 1,
Err(_) => eprintln!("[events] a subscriber to {} panicked", std::any::type_name::<E>()),
}
}
completed
}
pub fn subscribers<E: Any>(&self) -> usize {
self.subscribers.read().unwrap_or_else(|e| e.into_inner()).get(&TypeId::of::<E>()).map_or(0, Vec::len)
}
pub fn forward_to_mcp<E, F>(&self, server: crate::mcp::McpServer, method: &str, params: F) -> SubscriptionId
where
E: Any,
F: Fn(&E) -> String + Send + Sync + 'static,
{
let method = method.to_string();
self.subscribe(move |event: &E| server.notify(&method, Some(¶ms(event))))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn forward_to_channel<E, T, M>(&self, hub: crate::channels::Hub, topic: T, message: M) -> SubscriptionId
where
E: Any,
T: Fn(&E) -> String + Send + Sync + 'static,
M: Fn(&E) -> String + Send + Sync + 'static,
{
self.subscribe(move |event: &E| {
hub.publish(&topic(event), &message(event));
})
}
}