event_bus
Un bus d'événements asynchrone en mémoire pour Tokio, entièrement basé sur le typage fort en Rust (TypeId).
🎯 Problématique résolue
Dans l'implémentation initiale (event_bus), le modèle était inspiré de JavaScript (topics basés sur des chaînes de caractères) :
- Les événements étaient transportés via un pointeur générique
AnyEvent(Arc<dyn Any + Send + Sync>). - L'émission imposait de spécifier un topic string (
bus.emit("topic", event)). - La réception nécessitait de spécifier à la fois le type et le topic string (
bus.on_async::<Event, _, _>("topic", ...)), puis d'effectuer un déréférencement / downcast dynamique (downcast_ref::<T>()ou macromatch_event!) sur chaque message reçu.
event_bus résout entièrement cette complexité :
- Typage fort natif : le routage est directement effectué par l'identifiant de type (
std::any::TypeId), sans nom de topic requis. - Zéro downcast / déréférencement à la réception : le callback reçoit directement la structure d'événement typée.
- Syntaxe ergonomique : support complet de la syntaxe turbofish demandée
bus.on_async::<MessageUpdatedEvent>(|event| async move { ... })ainsi que de l'inférence automatiquebus.on_async(|event: MessageUpdatedEvent| async move { ... }). - Source unique de vérité : plus besoin de
Scopeexterne ; le contexte (ex:channel_id,server_id,caller_id) est directement transporté dans les champs de la structure typée. - Targeted wake-up : canaux Tokio
broadcastisolés par type d'événement, garantissant des performances optimales sans réveil inutile de tâches.
🚀 Utilisation
1. Définir des événements
N'importe quelle structure Rust implémentant Clone + Send + Sync + 'static est automatiquement un Event valide (aucun macro derive supplémentaire nécessaire) :
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct MessageCreatedEvent {
pub server_id: Option<Uuid>,
pub channel_id: Uuid,
pub content: String,
}
#[derive(Clone, Debug)]
pub struct MessageUpdatedEvent {
pub id: u64,
pub content: String,
}
2. Émission d'événements
use event_bus::EventBus;
let bus = EventBus::new();
// Émission typée directe
bus.emit(MessageUpdatedEvent {
id: 42,
content: "Nouveau message".into(),
});
3. Réception asynchrone (on_async)
Syntaxe turbofish exacte demandée :
bus.on_async::<MessageUpdatedEvent>(|event| async move {
// `event` est directement de type MessageUpdatedEvent
println!("Message {} mis à jour : {}", event.id, event.content);
});
Ou avec inférence sur l'argument de fermeture :
bus.on_async(|event: MessageUpdatedEvent| async move {
println!("Contenu : {}", event.content);
});
4. Réception synchrone (on)
bus.on::<MessageCreatedEvent>(|event| {
println!("Nouveau message créé sur le salon : {:?}", event.channel_id);
});
5. Utilisation avec contexte injecté (on_async_with)
Pratique pour passer des services ou repositories sans clones manuels répétés :
bus.on_async_with::<MessageCreatedEvent, _>(router, |router, event| async move {
router.gateway.send(...);
});
6. Écouteurs One-Shot (wait_next et wait_for)
Permet d'attendre un événement de manière linéaire avec une Future (sans boucle manuelle ni fuite de souscription) :
// Attend le tout prochain événement de ce type
let event = bus.wait_next::<MessageCreatedEvent>().await?;
// Ou attend un événement répondant à une condition précise
let confirmed = bus.wait_for::<MessageSavedEvent>(|e| e.id == target_id).await?;
7. Flux direct / Récepteur sans callback (subscribe)
Si vous préférez consommer les événements dans votre propre boucle de streaming :
let mut rx = bus.subscribe::<MessageUpdatedEvent>();
tokio::spawn(async move {
while let Ok(event) = rx.recv().await {
// `event` est directement MessageUpdatedEvent, aucun `match_event!` requis !
println!("Reçu : {}", event.content);
}
});
🧪 Tests et Benchmarks
Exécuter les tests du crate :
cargo test --manifest-path event_bus/Cargo.toml
Exécuter les benchmarks Criterion :
cargo bench --manifest-path event_bus/Cargo.toml