163 lines
5.0 KiB
Rust
163 lines
5.0 KiB
Rust
//! # Event Bus
|
|
//!
|
|
//! An asynchronous event bus for routing typed messages between modules
|
|
//! without direct coupling.
|
|
//!
|
|
//! ## Features
|
|
//!
|
|
//! - **Key → event mapping**: each topic (`&str`) is independent
|
|
//! - **Type-unrestricted**: any `T: Any + Send + Sync + Clone`
|
|
//! - **Targeted wake-up**: only subscribers of the matching topic are woken up
|
|
//! - **Callback API**: JavaScript-style — `bus.on("topic", |payload| { ... })`
|
|
//! - **Glob pattern**: `on_pattern("user-*", |topic, payload| { ... })`
|
|
//! - **Async handlers**: `on_async` and `on_pattern_async`
|
|
//!
|
|
//! ## Example — sync callback
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use std::sync::Arc;
|
|
//! use oxspeak_server_lib::event_bus::EventBus;
|
|
//!
|
|
//! #[derive(Clone, Debug)]
|
|
//! struct User { name: String }
|
|
//!
|
|
//! # tokio_test::block_on(async {
|
|
//! let bus = Arc::new(EventBus::new());
|
|
//!
|
|
//! bus.on::<User>("user-connected", |user| {
|
|
//! println!("Connected: {:?}", user);
|
|
//! });
|
|
//!
|
|
//! bus.emit("user-connected", User { name: "Alice".into() });
|
|
//! # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
|
//! # });
|
|
//! ```
|
|
//!
|
|
//! ## Example — async callback
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use std::sync::Arc;
|
|
//! use oxspeak_server_lib::event_bus::EventBus;
|
|
//!
|
|
//! #[derive(Clone, Debug)]
|
|
//! struct User { name: String }
|
|
//!
|
|
//! # tokio_test::block_on(async {
|
|
//! let bus = Arc::new(EventBus::new());
|
|
//!
|
|
//! bus.on_async::<User, _, _>("user-connected", |user| async move {
|
|
//! println!("(async) Connected: {:?}", user);
|
|
//! });
|
|
//!
|
|
//! bus.emit("user-connected", User { name: "Bob".into() });
|
|
//! # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
|
//! # });
|
|
//! ```
|
|
//!
|
|
//! ## Example — glob pattern (topic included in the callback)
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use std::sync::Arc;
|
|
//! use oxspeak_server_lib::event_bus::EventBus;
|
|
//!
|
|
//! #[derive(Clone, Debug)]
|
|
//! struct User { name: String }
|
|
//!
|
|
//! # tokio_test::block_on(async {
|
|
//! let bus = Arc::new(EventBus::new());
|
|
//!
|
|
//! bus.on_pattern::<User, _>("user-*", |topic, user| {
|
|
//! match topic.as_str() {
|
|
//! "user-created" => println!("Created : {:?}", user),
|
|
//! "user-deleted" => println!("Deleted : {:?}", user),
|
|
//! other => println!("{}: {:?}", other, user),
|
|
//! }
|
|
//! });
|
|
//!
|
|
//! bus.emit("user-created", User { name: "Alice".into() });
|
|
//! bus.emit("user-deleted", User { name: "Bob".into() });
|
|
//! # tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
|
//! # });
|
|
//! ```
|
|
//!
|
|
//! ## Example — multi-type with `match_event!` (advanced)
|
|
//!
|
|
//! ```rust,no_run
|
|
//! use std::sync::Arc;
|
|
//! use oxspeak_server_lib::event_bus::EventBus;
|
|
//! use oxspeak_server_lib::match_event;
|
|
//!
|
|
//! #[derive(Clone, Debug)] struct User { name: String }
|
|
//! #[derive(Clone, Debug)] struct UdpMetric { value: f32 }
|
|
//!
|
|
//! # tokio_test::block_on(async {
|
|
//! let bus = Arc::new(EventBus::new());
|
|
//! let mut rx = bus.on_raw("mixed-topic");
|
|
//!
|
|
//! bus.emit("mixed-topic", User { name: "Alice".into() });
|
|
//!
|
|
//! if let Ok(evt) = rx.recv().await {
|
|
//! match_event!(evt,
|
|
//! User => |u| println!("User: {:?}", u),
|
|
//! UdpMetric => |m| println!("Metric: {:?}", m),
|
|
//! );
|
|
//! }
|
|
//! # });
|
|
//! ```
|
|
|
|
mod bus;
|
|
|
|
// Public re-exports
|
|
pub use bus::{AnyEvent, EventBus};
|
|
|
|
/// Downcasts an [`AnyEvent`] to one or more concrete types and executes
|
|
/// the matching closure if the type matches.
|
|
///
|
|
/// Non-matching branches are silently ignored.
|
|
///
|
|
/// # Syntax
|
|
/// ```text
|
|
/// match_event!(evt, Type1 => |val| { ... }, Type2 => |val| { ... })
|
|
/// ```
|
|
///
|
|
/// # Example
|
|
/// ```rust,no_run
|
|
/// # use std::sync::Arc;
|
|
/// # use oxspeak_server_lib::event_bus::EventBus;
|
|
/// # use oxspeak_server_lib::match_event;
|
|
/// # #[derive(Clone, Debug)] struct User { name: String }
|
|
/// # #[derive(Clone, Debug)] struct UdpMetric { value: f32 }
|
|
/// # let bus = Arc::new(EventBus::new());
|
|
/// # tokio_test::block_on(async {
|
|
/// let mut rx = bus.on_raw("user-connected");
|
|
/// bus.emit("user-connected", User { name: "Alice".into() });
|
|
///
|
|
/// if let Ok(evt) = rx.recv().await {
|
|
/// match_event!(evt,
|
|
/// User => |u| println!("User: {:?}", u),
|
|
/// UdpMetric => |m| println!("Metric: {:?}", m),
|
|
/// );
|
|
/// }
|
|
/// # });
|
|
/// ```
|
|
#[macro_export]
|
|
macro_rules! match_event {
|
|
($evt:expr, $($type:ty => $handler:expr),+ $(,)?) => {
|
|
$(
|
|
if let Some(val) = ($evt).downcast_ref::<$type>() {
|
|
($handler)(val.clone());
|
|
} else
|
|
)+
|
|
{
|
|
// No matching type → silently ignored
|
|
}
|
|
};
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Tests
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|