add event_bus_typed
This commit is contained in:
@@ -16,8 +16,8 @@ harness = false
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "sync"] }
|
||||
parking_lot = "0.12.5"
|
||||
tracing = "0.1"
|
||||
uuid = { version = "1.26.1", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "time", "sync"] }
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
uuid = { version = "1.26.1", features = ["v4"] }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# event_bus_typed
|
||||
# event_bus
|
||||
|
||||
Un bus d'événements asynchrone en mémoire pour Tokio, entièrement basé sur le typage fort en Rust (`TypeId`).
|
||||
|
||||
@@ -11,7 +11,7 @@ Dans l'implémentation initiale (`event_bus`), le modèle était inspiré de Jav
|
||||
- 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 macro `match_event!`) sur chaque message reçu.
|
||||
|
||||
**`event_bus_typed` résout entièrement cette complexité :**
|
||||
**`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 automatique `bus.on_async(|event: MessageUpdatedEvent| async move { ... })`.
|
||||
@@ -48,7 +48,7 @@ pub struct MessageUpdatedEvent {
|
||||
### 2. Émission d'événements
|
||||
|
||||
```rust
|
||||
use event_bus_typed::EventBus;
|
||||
use event_bus::EventBus;
|
||||
|
||||
let bus = EventBus::new();
|
||||
|
||||
@@ -139,10 +139,10 @@ tokio::spawn(async move {
|
||||
|
||||
Exécuter les tests du crate :
|
||||
```bash
|
||||
cargo test --manifest-path event_bus_typed/Cargo.toml
|
||||
cargo test --manifest-path event_bus/Cargo.toml
|
||||
```
|
||||
|
||||
Exécuter les benchmarks Criterion :
|
||||
```bash
|
||||
cargo bench --manifest-path event_bus_typed/Cargo.toml
|
||||
cargo bench --manifest-path event_bus/Cargo.toml
|
||||
```
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
@@ -8,8 +10,6 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main};
|
||||
use event_bus::EventBus;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
const TOPIC: &str = "bench-topic";
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SmallEvent {
|
||||
value: u64,
|
||||
@@ -38,57 +38,46 @@ fn runtime() -> Runtime {
|
||||
Runtime::new().expect("failed to create tokio runtime")
|
||||
}
|
||||
|
||||
fn wait_until_received(
|
||||
received: &AtomicU64,
|
||||
expected: u64,
|
||||
) -> impl std::future::Future<Output = ()> + '_ {
|
||||
async move {
|
||||
while received.load(Ordering::Relaxed) < expected {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
async fn wait_until_received(received: &AtomicU64, expected: u64) {
|
||||
while received.load(Ordering::Relaxed) < expected {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_emit_no_subscriber(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("event_bus/no_subscriber");
|
||||
let mut group = c.benchmark_group("event_bus_typed/no_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
let bus = EventBus::with_capacity(1024);
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(TOPIC, 42_u64);
|
||||
bus.emit(42_u64);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(TOPIC, SmallEvent { value: 42 });
|
||||
bus.emit(SmallEvent { value: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
StringEvent {
|
||||
id: 42,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
},
|
||||
);
|
||||
bus.emit(StringEvent {
|
||||
id: 42,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
VecEvent {
|
||||
id: 42,
|
||||
payload: vec![7_u8; 1024],
|
||||
},
|
||||
);
|
||||
bus.emit(VecEvent {
|
||||
id: 42,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,13 +85,10 @@ fn bench_emit_no_subscriber(c: &mut Criterion) {
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
ArcPayloadEvent {
|
||||
id: 42,
|
||||
payload: Arc::clone(&shared_payload),
|
||||
},
|
||||
);
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: 42,
|
||||
payload: Arc::clone(&shared_payload),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,13 +98,13 @@ fn bench_emit_no_subscriber(c: &mut Criterion) {
|
||||
fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus/raw_subscriber");
|
||||
let mut group = c.benchmark_group("event_bus_typed/raw_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let mut rx = bus.on_raw(TOPIC);
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<u64>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
@@ -134,23 +120,21 @@ fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(TOPIC, i);
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
receiver.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let mut rx = bus.on_raw(TOPIC);
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<SmallEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
@@ -166,23 +150,21 @@ fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(TOPIC, SmallEvent { value: i });
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
receiver.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let mut rx = bus.on_raw(TOPIC);
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<StringEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
@@ -198,30 +180,25 @@ fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
},
|
||||
);
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
receiver.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let mut rx = bus.on_raw(TOPIC);
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<VecEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
@@ -237,62 +214,56 @@ fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
},
|
||||
);
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
receiver.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let mut rx = bus.on_raw(TOPIC);
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<ArcPayloadEvent>();
|
||||
|
||||
let payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
ArcPayloadEvent {
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
receiver.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,17 +273,17 @@ fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
fn bench_typed_callback(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus/typed_callback");
|
||||
let mut group = c.benchmark_group("event_bus_typed/typed_callback");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<u64, _>(TOPIC, move |event| {
|
||||
let subscription = bus.on::<u64>(move |event| {
|
||||
let _ = event;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
@@ -320,27 +291,25 @@ fn bench_typed_callback(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(TOPIC, i);
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
subscription.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent, _>(TOPIC, move |event| {
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
@@ -348,27 +317,25 @@ fn bench_typed_callback(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(TOPIC, SmallEvent { value: i });
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
subscription.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<StringEvent, _>(TOPIC, move |event| {
|
||||
let subscription = bus.on::<StringEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.name.len();
|
||||
let _ = event.message.len();
|
||||
@@ -378,34 +345,29 @@ fn bench_typed_callback(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
},
|
||||
);
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
subscription.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<VecEvent, _>(TOPIC, move |event| {
|
||||
let subscription = bus.on::<VecEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -414,58 +376,53 @@ fn bench_typed_callback(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
},
|
||||
);
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
subscription.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<ArcPayloadEvent, _>(TOPIC, move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
let subscription = bus.on::<ArcPayloadEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(
|
||||
TOPIC,
|
||||
ArcPayloadEvent {
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
subscription.abort();
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
@@ -475,13 +432,13 @@ fn bench_typed_callback(c: &mut Criterion) {
|
||||
fn bench_multiple_subscribers(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus/multiple_subscribers");
|
||||
let mut group = c.benchmark_group("event_bus_typed/multiple_subscribers");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for subscriber_count in [1_u64, 2, 4, 8, 16, 32] {
|
||||
group.bench_function(format!("{subscriber_count}_subscribers"), |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = Arc::new(EventBus::with_capacity(iters as usize + 1024));
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let expected = iters * subscriber_count;
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
@@ -490,7 +447,7 @@ fn bench_multiple_subscribers(c: &mut Criterion) {
|
||||
for _ in 0..subscriber_count {
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent, _>(TOPIC, move |event| {
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
@@ -501,7 +458,7 @@ fn bench_multiple_subscribers(c: &mut Criterion) {
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(TOPIC, SmallEvent { value: i });
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, expected).await;
|
||||
|
||||
+410
-401
@@ -1,420 +1,90 @@
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::task::JoinHandle;
|
||||
// use tracing::log::kv::{Key, Value};
|
||||
use tracing::{debug, trace, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Raw event type: an atomic reference-counted pointer to any value.
|
||||
pub type AnyEvent = Arc<dyn Any + Send + Sync>;
|
||||
use crate::handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
/// Default buffer capacity for each broadcast channel.
|
||||
const DEFAULT_CAPACITY: usize = 64;
|
||||
pub const DEFAULT_CAPACITY: usize = 1024;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ScopeValue {
|
||||
String(String),
|
||||
Uuid(Uuid),
|
||||
}
|
||||
impl ScopeValue {
|
||||
fn into_string(self) -> String {
|
||||
match self {
|
||||
Self::String(value) => value,
|
||||
Self::Uuid(value) => value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Marker trait for events dispatched through [`EventBus`].
|
||||
///
|
||||
/// Any type implementing `Clone + Send + Sync + 'static` automatically
|
||||
/// implements `Event`.
|
||||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Scope {
|
||||
pub key: String,
|
||||
pub value: ScopeValue,
|
||||
}
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(key: impl Into<String>, value: ScopeValue) -> Self {
|
||||
Self {
|
||||
key: key.into(),
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uuid(key: impl Into<String>, value: Uuid) -> Self {
|
||||
Self::new(key, ScopeValue::Uuid(value))
|
||||
}
|
||||
|
||||
pub fn string(key: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
Self::new(key, ScopeValue::String(value.into()))
|
||||
}
|
||||
}
|
||||
impl IntoIterator for Scope {
|
||||
type Item = Scope;
|
||||
type IntoIter = iter::Once<Scope>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The central event bus.
|
||||
///
|
||||
/// Share it via `Arc<EventBus>` across modules.
|
||||
/// Each topic has its own broadcast channel: only subscribers of the matching
|
||||
/// topic are woken up on `emit` (targeted wake-up).
|
||||
///
|
||||
/// # Minimal 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;
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct EventBus {
|
||||
/// Channels indexed by exact topic.
|
||||
channels: RwLock<HashMap<String, broadcast::Sender<AnyEvent>>>,
|
||||
struct EventBusInner {
|
||||
channels: RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Creates a bus with the default capacity (64 messages per channel).
|
||||
pub fn new() -> Self {
|
||||
debug!(
|
||||
"EventBus created with default capacity ({})",
|
||||
DEFAULT_CAPACITY
|
||||
);
|
||||
Self {
|
||||
channels: RwLock::new(HashMap::new()),
|
||||
capacity: DEFAULT_CAPACITY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a bus with a custom buffer capacity.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
debug!("EventBus created with capacity {}", capacity);
|
||||
Self {
|
||||
channels: RwLock::new(HashMap::new()),
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Internal
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_or_create_sender(&self, topic: &str) -> broadcast::Sender<AnyEvent> {
|
||||
{
|
||||
let channels = self.channels.read();
|
||||
if let Some(tx) = channels.get(topic) {
|
||||
return tx.clone();
|
||||
}
|
||||
}
|
||||
let mut channels = self.channels.write();
|
||||
let created = !channels.contains_key(topic);
|
||||
let tx = channels
|
||||
.entry(topic.to_string())
|
||||
.or_insert_with(|| {
|
||||
let (tx, _) = broadcast::channel(self.capacity);
|
||||
tx
|
||||
})
|
||||
.clone();
|
||||
if created {
|
||||
debug!(topic, "New broadcast channel created");
|
||||
}
|
||||
tx
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Emission
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Emits an event on a topic.
|
||||
///
|
||||
/// - Pushes the event into the exact-topic channel (if subscribers exist).
|
||||
/// - If nobody is listening, the event is silently dropped.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use std::sync::Arc;
|
||||
/// # use oxspeak_server_lib::event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct User;
|
||||
/// # let bus = Arc::new(EventBus::new());
|
||||
/// bus.emit("user-connected", User);
|
||||
/// bus.emit("user-deleted", uuid::Uuid::new_v4());
|
||||
/// ```
|
||||
pub fn emit<T: Any + Send + Sync + 'static>(&self, topic: &str, event: T) {
|
||||
trace!(topic, "Emitting event");
|
||||
let event: AnyEvent = Arc::new(event);
|
||||
|
||||
self.emit_arc(topic, event);
|
||||
}
|
||||
|
||||
// todo : undocumented...
|
||||
pub fn emit_scoped<T>(&self, topic: &str, scopes: impl IntoIterator<Item = Scope>, event: T)
|
||||
where
|
||||
T: Any + Send + Sync + 'static,
|
||||
{
|
||||
let event: AnyEvent = Arc::new(event);
|
||||
|
||||
// Émission sur le topic général.
|
||||
self.emit_arc(topic, Arc::clone(&event));
|
||||
|
||||
// Émission sur chaque topic scoped.
|
||||
for scope in scopes {
|
||||
let scoped_topic = format!("{}:{}:{}", scope.key, scope.value.into_string(), topic);
|
||||
|
||||
self.emit_arc(&scoped_topic, Arc::clone(&event));
|
||||
}
|
||||
}
|
||||
|
||||
// todo : undocumented...
|
||||
fn emit_arc(&self, topic: &str, event: AnyEvent) {
|
||||
trace!(topic, "Emitting event");
|
||||
|
||||
if let Some(tx) = self.channels.read().get(topic) {
|
||||
let receiver_count = tx.receiver_count();
|
||||
let _ = tx.send(event);
|
||||
|
||||
trace!(topic, receiver_count, "Event delivered to channel");
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Subscription — callbacks (main API)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Subscribes to a topic and calls `handler` on each event of type `T`.
|
||||
///
|
||||
/// The handler runs in a dedicated Tokio task (fire-and-forget).
|
||||
/// Events of a different type are silently ignored.
|
||||
/// Returns a [`JoinHandle`] to cancel the subscription if needed.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use std::sync::Arc;
|
||||
/// # use oxspeak_server_lib::event_bus::EventBus;
|
||||
/// # #[derive(Clone, Debug)] struct User { name: String }
|
||||
/// # let bus = Arc::new(EventBus::new());
|
||||
/// bus.on::<User>("user-connected", |user| {
|
||||
/// println!("Connected: {:?}", user);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on<T, F>(&self, topic: &str, handler: F) -> JoinHandle<()>
|
||||
where
|
||||
T: Any + Send + Sync + Clone + 'static,
|
||||
F: Fn(T) + Send + Sync + 'static,
|
||||
{
|
||||
let mut rx = self.get_or_create_sender(topic).subscribe();
|
||||
let topic_owned = topic.to_string();
|
||||
|
||||
debug!(topic, "Sync subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(evt) => {
|
||||
if let Some(typed) = evt.downcast_ref::<T>() {
|
||||
trace!(topic = topic_owned, "Sync handler invoked");
|
||||
handler(typed.clone());
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||
warn!(
|
||||
topic = topic_owned,
|
||||
skipped = n,
|
||||
"Subscriber lagged, messages dropped"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(
|
||||
topic = topic_owned,
|
||||
"Channel closed, sync subscriber exiting"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Subscribes to a topic and calls an **async** handler on each event of type `T`.
|
||||
///
|
||||
/// Ideal for performing async operations in the handler
|
||||
/// (DB query, HTTP call, WebSocket broadcast, …).
|
||||
/// Returns a [`JoinHandle`] to cancel the subscription if needed.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use std::sync::Arc;
|
||||
/// # use oxspeak_server_lib::event_bus::EventBus;
|
||||
/// # #[derive(Clone, Debug)] struct User { name: String }
|
||||
/// # let bus = Arc::new(EventBus::new());
|
||||
/// bus.on_async::<User, _, _>("user-connected", |user| async move {
|
||||
/// println!("(async) Connected: {:?}", user);
|
||||
/// // async work here: DB query, HTTP, etc.
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on_async<T, F, Fut>(&self, topic: &str, handler: F) -> JoinHandle<()>
|
||||
where
|
||||
T: Any + Send + Sync + Clone + 'static,
|
||||
F: Fn(T) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let mut rx = self.get_or_create_sender(topic).subscribe();
|
||||
let topic_owned = topic.to_string();
|
||||
|
||||
debug!(topic, "Async subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(evt) => {
|
||||
if let Some(typed) = evt.downcast_ref::<T>() {
|
||||
trace!(topic = topic_owned, "Async handler invoked");
|
||||
handler(typed.clone()).await;
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||
warn!(
|
||||
topic = topic_owned,
|
||||
skipped = n,
|
||||
"Subscriber lagged, messages dropped"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(
|
||||
topic = topic_owned,
|
||||
"Channel closed, async subscriber exiting"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// todo : Undocumented
|
||||
pub fn on_async_with<T, C, F, Fut>(&self, topic: &str, context: C, handler: F) -> JoinHandle<()>
|
||||
where
|
||||
T: Any + Send + Sync + Clone + 'static,
|
||||
C: Clone + Send + Sync + 'static,
|
||||
F: Fn(C, T) -> Fut + Send + Sync + 'static,
|
||||
Fut: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let mut rx = self.get_or_create_sender(topic).subscribe();
|
||||
let topic_owned = topic.to_string();
|
||||
|
||||
debug!(topic, "Async subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(evt) => {
|
||||
if let Some(typed) = evt.downcast_ref::<T>() {
|
||||
trace!(topic = topic_owned, "Async handler invoked");
|
||||
|
||||
handler(context.clone(), typed.clone()).await;
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||
warn!(
|
||||
topic = topic_owned,
|
||||
skipped = n,
|
||||
"Subscriber lagged, messages dropped"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(
|
||||
topic = topic_owned,
|
||||
"Channel closed, async subscriber exiting"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Subscription — low-level access (advanced use cases)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a raw [`AnyEvent`] receiver to manage the loop yourself.
|
||||
///
|
||||
/// Useful with the [`match_event!`][crate::match_event] macro to handle
|
||||
/// multiple different types on the same topic.
|
||||
///
|
||||
/// # 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),
|
||||
/// );
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn on_raw(&self, topic: &str) -> broadcast::Receiver<AnyEvent> {
|
||||
debug!(topic, "Raw subscriber registered");
|
||||
self.get_or_create_sender(topic).subscribe()
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns the list of currently registered topics.
|
||||
pub fn topics(&self) -> Vec<String> {
|
||||
self.channels.read().keys().cloned().collect()
|
||||
}
|
||||
/// A central, strongly-typed broadcast event bus.
|
||||
///
|
||||
/// Unlike string/topic-based event buses, [`EventBus`] dispatches events directly
|
||||
/// based on the concrete Rust type of the event (using [`TypeId`]).
|
||||
///
|
||||
/// Under the hood, each event type is backed by an independent [`tokio::sync::broadcast`]
|
||||
/// ring buffer. Dispatching an event via [`emit`](Self::emit) is $O(1)$ and non-blocking.
|
||||
///
|
||||
/// # Sync callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageCreatedEvent {
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on::<MessageCreatedEvent>(|event| {
|
||||
/// println!("Created message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageCreatedEvent {
|
||||
/// content: "Hello!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Async callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageUpdatedEvent {
|
||||
/// id: u64,
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("(async) Updated message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageUpdatedEvent {
|
||||
/// id: 42,
|
||||
/// content: "World!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventBus {
|
||||
inner: Arc<EventBusInner>,
|
||||
}
|
||||
|
||||
impl Default for EventBus {
|
||||
@@ -422,3 +92,342 @@ impl Default for EventBus {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Creates a new `EventBus` with default capacity (1024 messages per channel).
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(DEFAULT_CAPACITY)
|
||||
}
|
||||
|
||||
/// Creates a new `EventBus` with custom buffer capacity per channel.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
debug!(capacity, "EventBus created");
|
||||
Self {
|
||||
inner: Arc::new(EventBusInner {
|
||||
channels: RwLock::new(HashMap::new()),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Internal Channel Management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_or_create_sender<E: Event>(&self) -> broadcast::Sender<E> {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let mut channels = self.inner.channels.write();
|
||||
if let Some(tx) = channels
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let (tx, _) = broadcast::channel::<E>(self.inner.capacity);
|
||||
debug!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
capacity = self.inner.capacity,
|
||||
"New broadcast channel created"
|
||||
);
|
||||
channels.insert(type_id, Box::new(tx.clone()));
|
||||
tx
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Emission
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Emits a strongly-typed event to all subscribers of `E` without blocking ($O(1)$).
|
||||
///
|
||||
/// If no subscribers exist for this event type, the event is dropped immediately
|
||||
/// without cloning or allocating.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct UserConnected { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.emit(UserConnected { id: 1 });
|
||||
/// ```
|
||||
pub fn emit<E: Event>(&self, event: E) {
|
||||
trace!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
"Emitting event"
|
||||
);
|
||||
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
.filter(|tx| tx.receiver_count() > 0)
|
||||
{
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Subscriptions — Callbacks
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Registers a synchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
pub fn on<E: Event>(&self, handler: impl Fn(E) + Send + Sync + 'static) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Sync subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Sync handler invoked");
|
||||
handler(event);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageUpdatedEvent { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("Updated: {:?}", event.id);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on_async<E: Event>(&self, handler: impl AsyncHandler<E>) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler invoked");
|
||||
handler(event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E` with an injected context `C`.
|
||||
pub fn on_async_with<E: Event, C: Clone + Send + Sync + 'static>(
|
||||
&self,
|
||||
context: C,
|
||||
handler: impl AsyncHandlerWith<E, C>,
|
||||
) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber with context registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler with context invoked");
|
||||
handler(context.clone(), event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Low-Level Subscription (Direct Stream / Receiver)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a direct [`broadcast::Receiver<E>`] for events of type `E`.
|
||||
///
|
||||
/// Allows writing custom event processing loops without callback wrappers.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus::EventBus;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let mut rx = bus.subscribe::<MyEvent>();
|
||||
/// bus.emit(MyEvent);
|
||||
/// if let Ok(event) = rx.recv().await {
|
||||
/// // direct typed `event`
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn subscribe<E: Event>(&self) -> broadcast::Receiver<E> {
|
||||
self.get_or_create_sender::<E>().subscribe()
|
||||
}
|
||||
|
||||
/// Waits for the next event of type `E` to be emitted.
|
||||
///
|
||||
/// Creates a temporary one-shot subscription and resolves as soon as an event of type `E`
|
||||
/// is emitted. The subscription is automatically dropped after receiving the event.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct MyEvent { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let event = bus.wait_next::<MyEvent>().await.unwrap();
|
||||
/// println!("Received next event: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_next<E: Event>(&self) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
rx.recv().await
|
||||
}
|
||||
|
||||
/// Waits for an event of type `E` satisfying the given predicate to be emitted.
|
||||
///
|
||||
/// Creates a temporary subscription, receives events of type `E`, and resolves
|
||||
/// when the predicate returns `true`. The subscription is automatically dropped afterwards.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageSaved { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let target_id = 42;
|
||||
/// let event = bus.wait_for::<MessageSaved>(|e| e.id == target_id).await.unwrap();
|
||||
/// println!("Saved message confirmed: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_for<E: Event>(
|
||||
&self,
|
||||
mut predicate: impl FnMut(&E) -> bool,
|
||||
) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
if predicate(&event) {
|
||||
return Ok(event);
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
skipped,
|
||||
"wait_for subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
return Err(broadcast::error::RecvError::Closed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Metrics & Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns the total number of active subscribers for event type `E`.
|
||||
pub fn subscriber_count<E: Event>(&self) -> usize {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
self.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|e| e.downcast_ref::<broadcast::Sender<E>>())
|
||||
.map(|tx| tx.receiver_count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any active subscribers for event type `E`.
|
||||
pub fn has_subscribers<E: Event>(&self) -> bool {
|
||||
self.subscriber_count::<E>() > 0
|
||||
}
|
||||
|
||||
/// Returns the total number of broadcast channels currently instantiated in the bus.
|
||||
pub fn channel_count(&self) -> usize {
|
||||
self.inner.channels.read().len()
|
||||
}
|
||||
|
||||
/// Returns `true` if no channels are currently registered in the bus.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.channels.read().is_empty()
|
||||
}
|
||||
|
||||
/// Removes and drops all channels in the event bus.
|
||||
pub fn clear(&self) {
|
||||
self.inner.channels.write().clear();
|
||||
}
|
||||
|
||||
/// Removes and drops the channel associated with event type `E`.
|
||||
pub fn clear_type<E: Event>(&self) {
|
||||
let type_id = TypeId::of::<E>();
|
||||
self.inner.channels.write().remove(&type_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::future::Future;
|
||||
/// enabling seamless turbofish syntax with clean field access:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # use event_bus::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageUpdatedEvent { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
+44
-44
@@ -1,49 +1,49 @@
|
||||
/// 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
|
||||
}
|
||||
};
|
||||
}
|
||||
//! # event_bus
|
||||
//!
|
||||
//! A strongly-typed, high-performance in-memory event bus for Tokio.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Unlike string/topic-based event buses, `event_bus` routes events using
|
||||
//! their concrete Rust types ([`std::any::TypeId`]).
|
||||
//!
|
||||
//! - **Strong typing**: No string keys required for event types, no manual `match_event!`
|
||||
//! macros, and no runtime downcasting (`downcast_ref`) inside the subscriber loops.
|
||||
//! - **Ergonomic async subscribers**: Handlers can be registered with clean turbofish syntax:
|
||||
//! `bus.on_async::<MessageUpdatedEvent>(|event| async move { ... })`.
|
||||
//! - **Targeted wake-up**: Tokio broadcast channels are isolated per event type.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use event_bus::EventBus;
|
||||
//!
|
||||
//! #[derive(Clone, Debug, PartialEq)]
|
||||
//! struct MessageCreatedEvent {
|
||||
//! content: String,
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let bus = EventBus::new();
|
||||
//!
|
||||
//! // Async subscriber
|
||||
//! bus.on_async::<MessageCreatedEvent>(|event| async move {
|
||||
//! println!("Received message: {}", event.content);
|
||||
//! });
|
||||
//!
|
||||
//! // Emit event
|
||||
//! bus.emit(MessageCreatedEvent {
|
||||
//! content: "Hello from typed event bus!".into(),
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod bus;
|
||||
pub use bus::{AnyEvent, EventBus, Scope, ScopeValue};
|
||||
mod handler;
|
||||
|
||||
pub use bus::{DEFAULT_CAPACITY, Event, EventBus};
|
||||
pub use handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
+314
-142
@@ -1,192 +1,364 @@
|
||||
use crate::{EventBus, match_event};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::EventBus;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct User {
|
||||
name: String,
|
||||
struct MessageCreatedEvent {
|
||||
channel_id: Uuid,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct UdpMetric {
|
||||
struct MessageUpdatedEvent {
|
||||
id: u64,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageDeletedEvent {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct UdpMetricEvent {
|
||||
value: f32,
|
||||
}
|
||||
|
||||
// ── on (callback sync) ────────────────────────────────────────────────────
|
||||
// ── Sync Callbacks ──────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_callback_sync() {
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let bus = EventBus::new();
|
||||
let received = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&received);
|
||||
|
||||
bus.on::<User, _>("user-connected", move |user| {
|
||||
if user.name == "Alice" {
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Hello" {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(
|
||||
"user-connected",
|
||||
User {
|
||||
name: "Alice".into(),
|
||||
},
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(received.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_targeted_wakeup() {
|
||||
// Émettre sur "user-connected" ne doit pas réveiller "udp-metrics-updated"
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let metric_called = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&metric_called);
|
||||
|
||||
bus.on::<UdpMetric, _>("udp-metrics-updated", move |_| {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
bus.emit(
|
||||
"user-connected",
|
||||
User {
|
||||
name: "Carol".into(),
|
||||
},
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
assert!(!metric_called.load(Ordering::SeqCst));
|
||||
}
|
||||
// ── Async Callbacks (Exact User Requirement) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_type_mismatch_ignored() {
|
||||
// Émettre un UdpMetric sur un topic écouté en User → handler pas appelé
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let called = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&called);
|
||||
async fn test_on_async_callback_turbofish() {
|
||||
let bus = EventBus::new();
|
||||
let received_content = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let rc = Arc::clone(&received_content);
|
||||
|
||||
bus.on::<User, _>("mixed-topic", move |_| {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
bus.emit("mixed-topic", UdpMetric { value: 1.0 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
assert!(!called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_multiple_subscribers_same_topic() {
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
|
||||
for _ in 0..3 {
|
||||
let c = Arc::clone(&count);
|
||||
bus.on::<User, _>("user-connected", move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
bus.emit(
|
||||
"user-connected",
|
||||
User {
|
||||
name: "Grace".into(),
|
||||
},
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
assert_eq!(count.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
// ── on_async ──────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback() {
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let received = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&received);
|
||||
|
||||
bus.on_async::<User, _, _>("user-connected", move |user| {
|
||||
let f = Arc::clone(&flag);
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.on_async::<MessageUpdatedEvent>(|event| async move { ... });
|
||||
bus.on_async::<MessageUpdatedEvent>(move |event| {
|
||||
let rc = Arc::clone(&rc);
|
||||
async move {
|
||||
if user.name == "Async" {
|
||||
let mut lock = rc.lock().await;
|
||||
*lock = event.content;
|
||||
}
|
||||
});
|
||||
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.emit(MessageUpdatedEvent { ... });
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 42,
|
||||
content: "Updated message content".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
let result = received_content.lock().await.clone();
|
||||
assert_eq!(result, "Updated message content");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback_type_inferred() {
|
||||
let bus = EventBus::new();
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let f = Arc::clone(&flag);
|
||||
|
||||
// Also supports inferring the event type from closure parameter
|
||||
bus.on_async(move |event: MessageUpdatedEvent| {
|
||||
let f = Arc::clone(&f);
|
||||
async move {
|
||||
if event.id == 99 {
|
||||
f.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(
|
||||
"user-connected",
|
||||
User {
|
||||
name: "Async".into(),
|
||||
},
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 99,
|
||||
content: "Inferred".into(),
|
||||
});
|
||||
|
||||
assert!(received.load(Ordering::SeqCst));
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── on_raw + match_event! ─────────────────────────────────────────────────
|
||||
// ── Targeted Wake-Up & Type Isolation ───────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_raw_and_match_event_macro() {
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let mut rx = bus.on_raw("user-connected");
|
||||
|
||||
bus.emit(
|
||||
"user-connected",
|
||||
User {
|
||||
name: "Frank".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let evt = rx.recv().await.unwrap();
|
||||
let mut received_name = String::new();
|
||||
match_event!(evt,
|
||||
User => |u: User| { received_name = u.name.clone(); },
|
||||
UdpMetric => |_m: UdpMetric| { panic!("mauvais type"); }
|
||||
);
|
||||
assert_eq!(received_name, "Frank");
|
||||
}
|
||||
|
||||
// ── Utilitaires ────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_topics_list() {
|
||||
async fn test_on_targeted_wakeup() {
|
||||
let bus = EventBus::new();
|
||||
// on_raw enregistre le canal (get_or_create)
|
||||
let _rx1 = bus.on_raw("user-connected");
|
||||
let _rx2 = bus.on_raw("udp-metrics-updated");
|
||||
let metric_called = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&metric_called);
|
||||
|
||||
let mut topics = bus.topics();
|
||||
topics.sort();
|
||||
assert_eq!(topics, vec!["udp-metrics-updated", "user-connected"]);
|
||||
bus.on::<UdpMetricEvent>(move |_| {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emitting MessageCreatedEvent must never wake up UdpMetricEvent subscribers
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Ignore me".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(!metric_called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_emit_multiple_types_same_bus() {
|
||||
let bus = Arc::new(EventBus::new());
|
||||
let user_ok = Arc::new(AtomicBool::new(false));
|
||||
let metric_ok = Arc::new(AtomicBool::new(false));
|
||||
let u = Arc::clone(&user_ok);
|
||||
let m = Arc::clone(&metric_ok);
|
||||
async fn test_multiple_subscribers_same_type() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
|
||||
bus.on::<User, _>("user-connected", move |user| {
|
||||
if user.name == "Bob" {
|
||||
u.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
bus.on::<UdpMetric, _>("udp-metrics-updated", move |metric| {
|
||||
if (metric.value - 3.14).abs() < 0.001 {
|
||||
m.store(true, Ordering::SeqCst);
|
||||
}
|
||||
for _ in 0..3 {
|
||||
let c = Arc::clone(&count);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Broadcast".into(),
|
||||
});
|
||||
|
||||
bus.emit("user-connected", User { name: "Bob".into() });
|
||||
bus.emit("udp-metrics-updated", UdpMetric { value: 3.14 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
assert!(user_ok.load(Ordering::SeqCst));
|
||||
#[tokio::test]
|
||||
async fn test_multiple_different_types_same_bus() {
|
||||
let bus = EventBus::new();
|
||||
let msg_ok = Arc::new(AtomicBool::new(false));
|
||||
let metric_ok = Arc::new(AtomicBool::new(false));
|
||||
let m_flag = Arc::clone(&msg_ok);
|
||||
let u_flag = Arc::clone(&metric_ok);
|
||||
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Test" {
|
||||
m_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.on::<UdpMetricEvent>(move |metric| {
|
||||
if (metric.value - 42.5).abs() < 0.001 {
|
||||
u_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Test".into(),
|
||||
});
|
||||
bus.emit(UdpMetricEvent { value: 42.5 });
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(msg_ok.load(Ordering::SeqCst));
|
||||
assert!(metric_ok.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Direct Stream / Receiver (No match_event! needed) ───────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscribe_direct_typed_receiver() {
|
||||
let bus = EventBus::new();
|
||||
let mut rx = bus.subscribe::<MessageUpdatedEvent>();
|
||||
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 123,
|
||||
content: "Direct typed".into(),
|
||||
});
|
||||
|
||||
let event = rx.recv().await.expect("failed to receive event");
|
||||
// event is directly of type MessageUpdatedEvent, no downcast needed!
|
||||
assert_eq!(event.id, 123);
|
||||
assert_eq!(event.content, "Direct typed");
|
||||
}
|
||||
|
||||
// ── In-Handler Filtering (Direct Field Access) ──────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_filter_by_field_in_subscriber() {
|
||||
let bus = EventBus::new();
|
||||
let channel_a = Uuid::new_v4();
|
||||
let channel_b = Uuid::new_v4();
|
||||
|
||||
let count_a = Arc::new(AtomicU32::new(0));
|
||||
let count_b = Arc::new(AtomicU32::new(0));
|
||||
let count_global = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let ca = Arc::clone(&count_a);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let ca = Arc::clone(&ca);
|
||||
async move {
|
||||
if event.channel_id == channel_a {
|
||||
ca.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cb = Arc::clone(&count_b);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let cb = Arc::clone(&cb);
|
||||
async move {
|
||||
if event.channel_id == channel_b {
|
||||
cb.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cg = Arc::clone(&count_global);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
cg.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emit event with channel_a
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: channel_a,
|
||||
content: "For A".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
// Both channel_a handler and global handler processed it, but not channel_b
|
||||
assert_eq!(count_a.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(count_b.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(count_global.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── Async with Context ──────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_with_context() {
|
||||
let bus = EventBus::new();
|
||||
let prefix = Arc::new("Prefix: ".to_string());
|
||||
let result = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let r = Arc::clone(&result);
|
||||
|
||||
bus.on_async_with::<MessageCreatedEvent, _>(prefix, move |ctx, event| {
|
||||
let r = Arc::clone(&r);
|
||||
async move {
|
||||
let mut lock = r.lock().await;
|
||||
*lock = format!("{}{}", ctx, event.content);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello Context".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
let final_str = result.lock().await.clone();
|
||||
assert_eq!(final_str, "Prefix: Hello Context");
|
||||
}
|
||||
|
||||
// ── Metrics, Utilities & Cleanup ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_subscriber_count_and_clear() {
|
||||
let bus = EventBus::new();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert!(!bus.has_subscribers::<MessageCreatedEvent>());
|
||||
|
||||
let _sub = bus.subscribe::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 1);
|
||||
assert!(bus.has_subscribers::<MessageCreatedEvent>());
|
||||
assert_eq!(bus.channel_count(), 1);
|
||||
|
||||
bus.clear_type::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert_eq!(bus.channel_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscription_abort() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
let c = Arc::clone(&count);
|
||||
|
||||
let handle = bus.on::<MessageDeletedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 1 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
|
||||
// Cancel the subscription
|
||||
handle.abort();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 2 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
// Count should not increase after abort
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── One-Shot Listeners (wait_next & wait_for) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_next() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 777,
|
||||
content: "Next event".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus.wait_next::<MessageUpdatedEvent>().await.unwrap();
|
||||
assert_eq!(event.id, 777);
|
||||
assert_eq!(event.content, "Next event");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_for() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 1,
|
||||
content: "Ignore".into(),
|
||||
});
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 2,
|
||||
content: "Target".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus
|
||||
.wait_for::<MessageUpdatedEvent>(|e| e.id == 2)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(event.id, 2);
|
||||
assert_eq!(event.content, "Target");
|
||||
}
|
||||
|
||||
Generated
-798
@@ -1,798 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloca"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
|
||||
dependencies = [
|
||||
"alloca",
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"page_size",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "event_bus_typed"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"parking_lot",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "event_bus_typed"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
name = "event_bus_typed"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "event_bus_throughput"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "sync"] }
|
||||
parking_lot = "0.12.5"
|
||||
tracing = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.53.1", default-features = false, features = ["rt", "rt-multi-thread", "macros", "time", "sync"] }
|
||||
criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||
uuid = { version = "1.26.1", features = ["v4"] }
|
||||
@@ -1,488 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
|
||||
use event_bus_typed::EventBus;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SmallEvent {
|
||||
value: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StringEvent {
|
||||
id: u64,
|
||||
name: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct VecEvent {
|
||||
id: u64,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ArcPayloadEvent {
|
||||
id: u64,
|
||||
payload: Arc<[u8]>,
|
||||
}
|
||||
|
||||
fn runtime() -> Runtime {
|
||||
Runtime::new().expect("failed to create tokio runtime")
|
||||
}
|
||||
|
||||
async fn wait_until_received(received: &AtomicU64, expected: u64) {
|
||||
while received.load(Ordering::Relaxed) < expected {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_emit_no_subscriber(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("event_bus_typed/no_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
let bus = EventBus::with_capacity(1024);
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(42_u64);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(SmallEvent { value: 42 });
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(StringEvent {
|
||||
id: 42,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(VecEvent {
|
||||
id: 42,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
b.iter(|| {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: 42,
|
||||
payload: Arc::clone(&shared_payload),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_raw_subscriber(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/raw_subscriber");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<u64>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<SmallEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<StringEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<VecEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
let mut rx = bus.subscribe::<ArcPayloadEvent>();
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let receiver_count = Arc::clone(&received);
|
||||
|
||||
let receiver = tokio::spawn(async move {
|
||||
while receiver_count.load(Ordering::Relaxed) < iters {
|
||||
if rx.recv().await.is_ok() {
|
||||
receiver_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
receiver.abort();
|
||||
elapsed
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_typed_callback(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/typed_callback");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
group.bench_function("u64", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<u64>(move |event| {
|
||||
let _ = event;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(i);
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("small_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("string_struct", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<StringEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.name.len();
|
||||
let _ = event.message.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(StringEvent {
|
||||
id: i,
|
||||
name: "Alice".to_string(),
|
||||
message: "hello from benchmark".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("vec_payload_1kb", |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<VecEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(VecEvent {
|
||||
id: i,
|
||||
payload: vec![7_u8; 1024],
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
|
||||
let shared_payload: Arc<[u8]> = Arc::from(vec![7_u8; 1024].into_boxed_slice());
|
||||
|
||||
group.bench_function("arc_payload_1kb", |b| {
|
||||
let shared = Arc::clone(&shared_payload);
|
||||
b.to_async(&rt).iter_custom(|iters| {
|
||||
let payload = Arc::clone(&shared);
|
||||
async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<ArcPayloadEvent>(move |event| {
|
||||
let _ = event.id;
|
||||
let _ = event.payload.len();
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(ArcPayloadEvent {
|
||||
id: i,
|
||||
payload: Arc::clone(&payload),
|
||||
});
|
||||
}
|
||||
|
||||
wait_until_received(&received, iters).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
subscription.abort();
|
||||
elapsed
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_multiple_subscribers(c: &mut Criterion) {
|
||||
let rt = runtime();
|
||||
|
||||
let mut group = c.benchmark_group("event_bus_typed/multiple_subscribers");
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for subscriber_count in [1_u64, 2, 4, 8, 16, 32] {
|
||||
group.bench_function(format!("{subscriber_count}_subscribers"), |b| {
|
||||
b.to_async(&rt).iter_custom(|iters| async move {
|
||||
let bus = EventBus::with_capacity(iters as usize + 1024);
|
||||
|
||||
let expected = iters * subscriber_count;
|
||||
let received = Arc::new(AtomicU64::new(0));
|
||||
let mut subscriptions = Vec::with_capacity(subscriber_count as usize);
|
||||
|
||||
for _ in 0..subscriber_count {
|
||||
let handler_count = Arc::clone(&received);
|
||||
|
||||
let subscription = bus.on::<SmallEvent>(move |event| {
|
||||
let _ = event.value;
|
||||
handler_count.fetch_add(1, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
subscriptions.push(subscription);
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
for i in 0..iters {
|
||||
bus.emit(SmallEvent { value: i });
|
||||
}
|
||||
|
||||
wait_until_received(&received, expected).await;
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
for subscription in subscriptions {
|
||||
subscription.abort();
|
||||
}
|
||||
|
||||
elapsed
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_emit_no_subscriber,
|
||||
bench_raw_subscriber,
|
||||
bench_typed_callback,
|
||||
bench_multiple_subscribers,
|
||||
);
|
||||
|
||||
criterion_main!(benches);
|
||||
@@ -1,433 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
/// Default buffer capacity for each broadcast channel.
|
||||
pub const DEFAULT_CAPACITY: usize = 1024;
|
||||
|
||||
/// Marker trait for events dispatched through [`EventBus`].
|
||||
///
|
||||
/// Any type implementing `Clone + Send + Sync + 'static` automatically
|
||||
/// implements `Event`.
|
||||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EventBusInner {
|
||||
channels: RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
/// A central, strongly-typed broadcast event bus.
|
||||
///
|
||||
/// Unlike string/topic-based event buses, [`EventBus`] dispatches events directly
|
||||
/// based on the concrete Rust type of the event (using [`TypeId`]).
|
||||
///
|
||||
/// Under the hood, each event type is backed by an independent [`tokio::sync::broadcast`]
|
||||
/// ring buffer. Dispatching an event via [`emit`](Self::emit) is $O(1)$ and non-blocking.
|
||||
///
|
||||
/// # Sync callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageCreatedEvent {
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on::<MessageCreatedEvent>(|event| {
|
||||
/// println!("Created message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageCreatedEvent {
|
||||
/// content: "Hello!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Async callback example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone, Debug)]
|
||||
/// struct MessageUpdatedEvent {
|
||||
/// id: u64,
|
||||
/// content: String,
|
||||
/// }
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
///
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("(async) Updated message: {:?}", event);
|
||||
/// });
|
||||
///
|
||||
/// bus.emit(MessageUpdatedEvent {
|
||||
/// id: 42,
|
||||
/// content: "World!".into(),
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EventBus {
|
||||
inner: Arc<EventBusInner>,
|
||||
}
|
||||
|
||||
impl Default for EventBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Creates a new `EventBus` with default capacity (1024 messages per channel).
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(DEFAULT_CAPACITY)
|
||||
}
|
||||
|
||||
/// Creates a new `EventBus` with custom buffer capacity per channel.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
debug!(capacity, "EventBus created");
|
||||
Self {
|
||||
inner: Arc::new(EventBusInner {
|
||||
channels: RwLock::new(HashMap::new()),
|
||||
capacity,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Internal Channel Management
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn get_or_create_sender<E: Event>(&self) -> broadcast::Sender<E> {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let mut channels = self.inner.channels.write();
|
||||
if let Some(tx) = channels
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
{
|
||||
return tx.clone();
|
||||
}
|
||||
|
||||
let (tx, _) = broadcast::channel::<E>(self.inner.capacity);
|
||||
debug!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
capacity = self.inner.capacity,
|
||||
"New broadcast channel created"
|
||||
);
|
||||
channels.insert(type_id, Box::new(tx.clone()));
|
||||
tx
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Emission
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Emits a strongly-typed event to all subscribers of `E` without blocking ($O(1)$).
|
||||
///
|
||||
/// If no subscribers exist for this event type, the event is dropped immediately
|
||||
/// without cloning or allocating.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct UserConnected { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.emit(UserConnected { id: 1 });
|
||||
/// ```
|
||||
pub fn emit<E: Event>(&self, event: E) {
|
||||
trace!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
"Emitting event"
|
||||
);
|
||||
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
if let Some(tx) = self
|
||||
.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|entry| entry.downcast_ref::<broadcast::Sender<E>>())
|
||||
.filter(|tx| tx.receiver_count() > 0)
|
||||
{
|
||||
let _ = tx.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Subscriptions — Callbacks
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Registers a synchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
pub fn on<E: Event>(&self, handler: impl Fn(E) + Send + Sync + 'static) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Sync subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Sync handler invoked");
|
||||
handler(event);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E`.
|
||||
///
|
||||
/// The handler runs in a dedicated background Tokio task.
|
||||
/// Returns a [`JoinHandle`] allowing to cancel the subscription via [`.abort()`](JoinHandle::abort).
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageUpdatedEvent { id: u64 }
|
||||
/// # let bus = EventBus::new();
|
||||
/// bus.on_async::<MessageUpdatedEvent>(|event| async move {
|
||||
/// println!("Updated: {:?}", event.id);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn on_async<E: Event>(&self, handler: impl AsyncHandler<E>) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler invoked");
|
||||
handler(event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Registers an asynchronous callback for events of type `E` with an injected context `C`.
|
||||
pub fn on_async_with<E: Event, C: Clone + Send + Sync + 'static>(
|
||||
&self,
|
||||
context: C,
|
||||
handler: impl AsyncHandlerWith<E, C>,
|
||||
) -> JoinHandle<()> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
let type_name = std::any::type_name::<E>();
|
||||
|
||||
debug!(event_type = type_name, "Async subscriber with context registered");
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
trace!(event_type = type_name, "Async handler with context invoked");
|
||||
handler(context.clone(), event).await;
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = type_name,
|
||||
skipped,
|
||||
"Subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
debug!(event_type = type_name, "Channel closed, subscriber exiting");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Low-Level Subscription (Direct Stream / Receiver)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns a direct [`broadcast::Receiver<E>`] for events of type `E`.
|
||||
///
|
||||
/// Allows writing custom event processing loops without callback wrappers.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// use event_bus_typed::EventBus;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let mut rx = bus.subscribe::<MyEvent>();
|
||||
/// bus.emit(MyEvent);
|
||||
/// if let Ok(event) = rx.recv().await {
|
||||
/// // direct typed `event`
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn subscribe<E: Event>(&self) -> broadcast::Receiver<E> {
|
||||
self.get_or_create_sender::<E>().subscribe()
|
||||
}
|
||||
|
||||
/// Waits for the next event of type `E` to be emitted.
|
||||
///
|
||||
/// Creates a temporary one-shot subscription and resolves as soon as an event of type `E`
|
||||
/// is emitted. The subscription is automatically dropped after receiving the event.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MyEvent { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let event = bus.wait_next::<MyEvent>().await.unwrap();
|
||||
/// println!("Received next event: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_next<E: Event>(&self) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
rx.recv().await
|
||||
}
|
||||
|
||||
/// Waits for an event of type `E` satisfying the given predicate to be emitted.
|
||||
///
|
||||
/// Creates a temporary subscription, receives events of type `E`, and resolves
|
||||
/// when the predicate returns `true`. The subscription is automatically dropped afterwards.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use event_bus_typed::EventBus;
|
||||
/// # #[derive(Clone)] struct MessageSaved { id: u64 }
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let bus = EventBus::new();
|
||||
/// let target_id = 42;
|
||||
/// let event = bus.wait_for::<MessageSaved>(|e| e.id == target_id).await.unwrap();
|
||||
/// println!("Saved message confirmed: {}", event.id);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn wait_for<E: Event>(
|
||||
&self,
|
||||
mut predicate: impl FnMut(&E) -> bool,
|
||||
) -> Result<E, broadcast::error::RecvError> {
|
||||
let mut rx = self.subscribe::<E>();
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(event) => {
|
||||
if predicate(&event) {
|
||||
return Ok(event);
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(skipped)) => {
|
||||
warn!(
|
||||
event_type = std::any::type_name::<E>(),
|
||||
skipped,
|
||||
"wait_for subscriber lagged behind and skipped messages"
|
||||
);
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
return Err(broadcast::error::RecvError::Closed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Metrics & Utilities
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns the total number of active subscribers for event type `E`.
|
||||
pub fn subscriber_count<E: Event>(&self) -> usize {
|
||||
let type_id = TypeId::of::<E>();
|
||||
|
||||
self.inner
|
||||
.channels
|
||||
.read()
|
||||
.get(&type_id)
|
||||
.and_then(|e| e.downcast_ref::<broadcast::Sender<E>>())
|
||||
.map(|tx| tx.receiver_count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any active subscribers for event type `E`.
|
||||
pub fn has_subscribers<E: Event>(&self) -> bool {
|
||||
self.subscriber_count::<E>() > 0
|
||||
}
|
||||
|
||||
/// Returns the total number of broadcast channels currently instantiated in the bus.
|
||||
pub fn channel_count(&self) -> usize {
|
||||
self.inner.channels.read().len()
|
||||
}
|
||||
|
||||
/// Returns `true` if no channels are currently registered in the bus.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.channels.read().is_empty()
|
||||
}
|
||||
|
||||
/// Removes and drops all channels in the event bus.
|
||||
pub fn clear(&self) {
|
||||
self.inner.channels.write().clear();
|
||||
}
|
||||
|
||||
/// Removes and drops the channel associated with event type `E`.
|
||||
pub fn clear_type<E: Event>(&self) {
|
||||
let type_id = TypeId::of::<E>();
|
||||
self.inner.channels.write().remove(&type_id);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
//! # event_bus_typed
|
||||
//!
|
||||
//! A strongly-typed, high-performance in-memory event bus for Tokio.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Unlike string/topic-based event buses, `event_bus_typed` routes events using
|
||||
//! their concrete Rust types ([`std::any::TypeId`]).
|
||||
//!
|
||||
//! - **Strong typing**: No string keys required for event types, no manual `match_event!`
|
||||
//! macros, and no runtime downcasting (`downcast_ref`) inside the subscriber loops.
|
||||
//! - **Ergonomic async subscribers**: Handlers can be registered with clean turbofish syntax:
|
||||
//! `bus.on_async::<MessageUpdatedEvent>(|event| async move { ... })`.
|
||||
//! - **Targeted wake-up**: Tokio broadcast channels are isolated per event type.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use event_bus_typed::EventBus;
|
||||
//!
|
||||
//! #[derive(Clone, Debug, PartialEq)]
|
||||
//! struct MessageCreatedEvent {
|
||||
//! content: String,
|
||||
//! }
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let bus = EventBus::new();
|
||||
//!
|
||||
//! // Async subscriber
|
||||
//! bus.on_async::<MessageCreatedEvent>(|event| async move {
|
||||
//! println!("Received message: {}", event.content);
|
||||
//! });
|
||||
//!
|
||||
//! // Emit event
|
||||
//! bus.emit(MessageCreatedEvent {
|
||||
//! content: "Hello from typed event bus!".into(),
|
||||
//! });
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod bus;
|
||||
mod handler;
|
||||
|
||||
pub use bus::{DEFAULT_CAPACITY, Event, EventBus};
|
||||
pub use handler::{AsyncHandler, AsyncHandlerWith};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,364 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::EventBus;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageCreatedEvent {
|
||||
channel_id: Uuid,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageUpdatedEvent {
|
||||
id: u64,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct MessageDeletedEvent {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct UdpMetricEvent {
|
||||
value: f32,
|
||||
}
|
||||
|
||||
// ── Sync Callbacks ──────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_callback_sync() {
|
||||
let bus = EventBus::new();
|
||||
let received = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&received);
|
||||
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Hello" {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(received.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Async Callbacks (Exact User Requirement) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback_turbofish() {
|
||||
let bus = EventBus::new();
|
||||
let received_content = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let rc = Arc::clone(&received_content);
|
||||
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.on_async::<MessageUpdatedEvent>(|event| async move { ... });
|
||||
bus.on_async::<MessageUpdatedEvent>(move |event| {
|
||||
let rc = Arc::clone(&rc);
|
||||
async move {
|
||||
let mut lock = rc.lock().await;
|
||||
*lock = event.content;
|
||||
}
|
||||
});
|
||||
|
||||
// Exact syntax specified by the user:
|
||||
// event_bus.emit(MessageUpdatedEvent { ... });
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 42,
|
||||
content: "Updated message content".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
let result = received_content.lock().await.clone();
|
||||
assert_eq!(result, "Updated message content");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_callback_type_inferred() {
|
||||
let bus = EventBus::new();
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let f = Arc::clone(&flag);
|
||||
|
||||
// Also supports inferring the event type from closure parameter
|
||||
bus.on_async(move |event: MessageUpdatedEvent| {
|
||||
let f = Arc::clone(&f);
|
||||
async move {
|
||||
if event.id == 99 {
|
||||
f.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 99,
|
||||
content: "Inferred".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Targeted Wake-Up & Type Isolation ───────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_targeted_wakeup() {
|
||||
let bus = EventBus::new();
|
||||
let metric_called = Arc::new(AtomicBool::new(false));
|
||||
let flag = Arc::clone(&metric_called);
|
||||
|
||||
bus.on::<UdpMetricEvent>(move |_| {
|
||||
flag.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emitting MessageCreatedEvent must never wake up UdpMetricEvent subscribers
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Ignore me".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(!metric_called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_subscribers_same_type() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
|
||||
for _ in 0..3 {
|
||||
let c = Arc::clone(&count);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Broadcast".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_different_types_same_bus() {
|
||||
let bus = EventBus::new();
|
||||
let msg_ok = Arc::new(AtomicBool::new(false));
|
||||
let metric_ok = Arc::new(AtomicBool::new(false));
|
||||
let m_flag = Arc::clone(&msg_ok);
|
||||
let u_flag = Arc::clone(&metric_ok);
|
||||
|
||||
bus.on::<MessageCreatedEvent>(move |event| {
|
||||
if event.content == "Test" {
|
||||
m_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.on::<UdpMetricEvent>(move |metric| {
|
||||
if (metric.value - 42.5).abs() < 0.001 {
|
||||
u_flag.store(true, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Test".into(),
|
||||
});
|
||||
bus.emit(UdpMetricEvent { value: 42.5 });
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert!(msg_ok.load(Ordering::SeqCst));
|
||||
assert!(metric_ok.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
// ── Direct Stream / Receiver (No match_event! needed) ───────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscribe_direct_typed_receiver() {
|
||||
let bus = EventBus::new();
|
||||
let mut rx = bus.subscribe::<MessageUpdatedEvent>();
|
||||
|
||||
bus.emit(MessageUpdatedEvent {
|
||||
id: 123,
|
||||
content: "Direct typed".into(),
|
||||
});
|
||||
|
||||
let event = rx.recv().await.expect("failed to receive event");
|
||||
// event is directly of type MessageUpdatedEvent, no downcast needed!
|
||||
assert_eq!(event.id, 123);
|
||||
assert_eq!(event.content, "Direct typed");
|
||||
}
|
||||
|
||||
// ── In-Handler Filtering (Direct Field Access) ──────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_filter_by_field_in_subscriber() {
|
||||
let bus = EventBus::new();
|
||||
let channel_a = Uuid::new_v4();
|
||||
let channel_b = Uuid::new_v4();
|
||||
|
||||
let count_a = Arc::new(AtomicU32::new(0));
|
||||
let count_b = Arc::new(AtomicU32::new(0));
|
||||
let count_global = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let ca = Arc::clone(&count_a);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let ca = Arc::clone(&ca);
|
||||
async move {
|
||||
if event.channel_id == channel_a {
|
||||
ca.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cb = Arc::clone(&count_b);
|
||||
bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let cb = Arc::clone(&cb);
|
||||
async move {
|
||||
if event.channel_id == channel_b {
|
||||
cb.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cg = Arc::clone(&count_global);
|
||||
bus.on::<MessageCreatedEvent>(move |_| {
|
||||
cg.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Emit event with channel_a
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: channel_a,
|
||||
content: "For A".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
|
||||
// Both channel_a handler and global handler processed it, but not channel_b
|
||||
assert_eq!(count_a.load(Ordering::SeqCst), 1);
|
||||
assert_eq!(count_b.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(count_global.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── Async with Context ──────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_on_async_with_context() {
|
||||
let bus = EventBus::new();
|
||||
let prefix = Arc::new("Prefix: ".to_string());
|
||||
let result = Arc::new(tokio::sync::Mutex::new(String::new()));
|
||||
let r = Arc::clone(&result);
|
||||
|
||||
bus.on_async_with::<MessageCreatedEvent, _>(prefix, move |ctx, event| {
|
||||
let r = Arc::clone(&r);
|
||||
async move {
|
||||
let mut lock = r.lock().await;
|
||||
*lock = format!("{}{}", ctx, event.content);
|
||||
}
|
||||
});
|
||||
|
||||
bus.emit(MessageCreatedEvent {
|
||||
channel_id: Uuid::new_v4(),
|
||||
content: "Hello Context".into(),
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
let final_str = result.lock().await.clone();
|
||||
assert_eq!(final_str, "Prefix: Hello Context");
|
||||
}
|
||||
|
||||
// ── Metrics, Utilities & Cleanup ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_subscriber_count_and_clear() {
|
||||
let bus = EventBus::new();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert!(!bus.has_subscribers::<MessageCreatedEvent>());
|
||||
|
||||
let _sub = bus.subscribe::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 1);
|
||||
assert!(bus.has_subscribers::<MessageCreatedEvent>());
|
||||
assert_eq!(bus.channel_count(), 1);
|
||||
|
||||
bus.clear_type::<MessageCreatedEvent>();
|
||||
assert_eq!(bus.subscriber_count::<MessageCreatedEvent>(), 0);
|
||||
assert_eq!(bus.channel_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_subscription_abort() {
|
||||
let bus = EventBus::new();
|
||||
let count = Arc::new(AtomicU32::new(0));
|
||||
let c = Arc::clone(&count);
|
||||
|
||||
let handle = bus.on::<MessageDeletedEvent>(move |_| {
|
||||
c.fetch_add(1, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 1 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
|
||||
// Cancel the subscription
|
||||
handle.abort();
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
bus.emit(MessageDeletedEvent { id: 2 });
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
// Count should not increase after abort
|
||||
assert_eq!(count.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
|
||||
// ── One-Shot Listeners (wait_next & wait_for) ────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_next() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 777,
|
||||
content: "Next event".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus.wait_next::<MessageUpdatedEvent>().await.unwrap();
|
||||
assert_eq!(event.id, 777);
|
||||
assert_eq!(event.content, "Next event");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_wait_for() {
|
||||
let bus = EventBus::new();
|
||||
let b = bus.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 1,
|
||||
content: "Ignore".into(),
|
||||
});
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
b.emit(MessageUpdatedEvent {
|
||||
id: 2,
|
||||
content: "Target".into(),
|
||||
});
|
||||
});
|
||||
|
||||
let event = bus
|
||||
.wait_for::<MessageUpdatedEvent>(|e| e.id == 2)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(event.id, 2);
|
||||
assert_eq!(event.content, "Target");
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::models::category;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CategoryCreatedEvent {
|
||||
pub category: category::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CategoryUpdatedEvent {
|
||||
pub category: category::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CategoryDeletedEvent {
|
||||
pub category: category::Model,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use crate::models::{channel_role_permission, channel_user_permission};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChannelUserPermissionCreatedEvent {
|
||||
pub permission: channel_user_permission::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChannelUserPermissionUpdatedEvent {
|
||||
pub channel_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub permission: Option<channel_user_permission::Model>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChannelUserPermissionDeletedEvent {
|
||||
pub permission: channel_user_permission::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChannelRolePermissionUpdatedEvent {
|
||||
pub channel_id: Uuid,
|
||||
pub server_id: Uuid,
|
||||
pub role_id: Uuid,
|
||||
pub permission: Option<channel_role_permission::Model>,
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
pub mod category;
|
||||
pub mod channel;
|
||||
pub mod channel_permission;
|
||||
pub mod emoji;
|
||||
pub mod message;
|
||||
pub mod role;
|
||||
pub mod server;
|
||||
pub mod server_permission;
|
||||
pub mod server_tree;
|
||||
pub mod user;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
use crate::models::{role, role_user};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleCreatedEvent {
|
||||
pub role: role::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleUpdatedEvent {
|
||||
pub role: role::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleDeletedEvent {
|
||||
pub role: role::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleUserCreatedEvent {
|
||||
pub role_user: role_user::Model,
|
||||
pub server_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleUserDeletedEvent {
|
||||
pub role_user: role_user::Model,
|
||||
pub server_id: Uuid,
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::models::server;
|
||||
use crate::models::server_user;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerCreatedEvent {
|
||||
@@ -13,4 +15,15 @@ pub struct ServerUpdatedEvent {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerDeletedEvent {
|
||||
pub server: server::Model,
|
||||
pub audience: Vec<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerUserCreatedEvent {
|
||||
pub server_user: server_user::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerUserDeletedEvent {
|
||||
pub server_user: server_user::Model,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use crate::models::{server_role_permission, server_user_permission};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerUserPermissionUpdatedEvent {
|
||||
pub server_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub permission: Option<server_user_permission::Model>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServerRolePermissionUpdatedEvent {
|
||||
pub server_id: Uuid,
|
||||
pub role_id: Uuid,
|
||||
pub permission: Option<server_role_permission::Model>,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use crate::models::user;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserCreatedEvent {
|
||||
pub user: user::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserUpdatedEvent {
|
||||
pub user: user::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserChangedEvent {
|
||||
pub user: user::Model,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserDeletedEvent {
|
||||
pub user: user::Model,
|
||||
}
|
||||
@@ -108,12 +108,9 @@ async fn create_channel(state: &AppState, ids: &[Uuid]) -> Result<channel::Model
|
||||
.services
|
||||
.realtime_registry
|
||||
.set_channel_users(channel.id, ids.iter().copied());
|
||||
state.event_bus.emit(
|
||||
"channel_created",
|
||||
ChannelCreatedEvent {
|
||||
channel: channel.clone(),
|
||||
},
|
||||
);
|
||||
state.event_bus.emit(ChannelCreatedEvent {
|
||||
channel: channel.clone(),
|
||||
});
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
|
||||
@@ -155,12 +155,9 @@ pub async fn create(
|
||||
..Default::default()
|
||||
};
|
||||
let created = state.services.emoji.create(model, name).await?;
|
||||
state.event_bus.emit(
|
||||
"emoji_created",
|
||||
EmojiCreatedEvent {
|
||||
emoji: created.clone(),
|
||||
},
|
||||
);
|
||||
state.event_bus.emit(EmojiCreatedEvent {
|
||||
emoji: created.clone(),
|
||||
});
|
||||
Ok((StatusCode::CREATED, Json(mapper::response(created))))
|
||||
}
|
||||
|
||||
@@ -201,13 +198,10 @@ pub async fn update(
|
||||
active.name = Set(EmojiService::normalize_name(&name)?);
|
||||
}
|
||||
let updated = state.repositories.emoji.update(active).await?;
|
||||
state.event_bus.emit(
|
||||
"emoji_updated",
|
||||
EmojiUpdatedEvent {
|
||||
previous,
|
||||
emoji: updated.clone(),
|
||||
},
|
||||
);
|
||||
state.event_bus.emit(EmojiUpdatedEvent {
|
||||
previous,
|
||||
emoji: updated.clone(),
|
||||
});
|
||||
Ok(Json(mapper::response(updated)))
|
||||
}
|
||||
|
||||
@@ -241,9 +235,7 @@ pub async fn delete(
|
||||
model.file_path.as_deref(),
|
||||
)
|
||||
.await;
|
||||
state
|
||||
.event_bus
|
||||
.emit("emoji_deleted", EmojiDeletedEvent { emoji: model });
|
||||
state.event_bus.emit(EmojiDeletedEvent { emoji: model });
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
} else {
|
||||
Err(HTTPError::NotFound)
|
||||
|
||||
+74
-77
@@ -1,4 +1,7 @@
|
||||
use crate::core::AppState;
|
||||
use crate::domain::events::category::{
|
||||
CategoryCreatedEvent, CategoryDeletedEvent, CategoryUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::channel::{
|
||||
ChannelCreatedEvent, ChannelDeletedEvent, ChannelUpdatedEvent,
|
||||
};
|
||||
@@ -7,8 +10,9 @@ use crate::domain::events::message::{
|
||||
MessageCreatedEvent, MessageDeletedEvent, MessageReactionAddedEvent,
|
||||
MessageReactionRemovedEvent, MessageUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::server::{ServerCreatedEvent, ServerDeletedEvent, ServerUpdatedEvent};
|
||||
use crate::domain::events::server_tree::ServerTreeInvalidatedEvent;
|
||||
use crate::models::{category, server, server_user, user};
|
||||
use crate::models::{server_user, user};
|
||||
use crate::repositories::Repositories;
|
||||
use crate::routes::category::mapper::category_model_to_category_response;
|
||||
use crate::routes::channel::mapper::channel_model_to_channel_response;
|
||||
@@ -112,7 +116,7 @@ impl RealtimeRouter {
|
||||
|
||||
pub fn start(self: &Arc<Self>, event_bus: Arc<EventBus>) {
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<MessageCreatedEvent, _, _>("message_created", move |event| {
|
||||
event_bus.on_async::<MessageCreatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let attachments = router
|
||||
@@ -142,7 +146,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<MessageUpdatedEvent, _, _>("message_updated", move |event| {
|
||||
event_bus.on_async::<MessageUpdatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let reactions = router
|
||||
@@ -171,7 +175,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<MessageDeletedEvent, _, _>("message_deleted", move |event| {
|
||||
event_bus.on_async::<MessageDeletedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
@@ -185,47 +189,41 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<MessageReactionAddedEvent, _, _>(
|
||||
"message_reaction_added",
|
||||
move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
.services
|
||||
.realtime_registry
|
||||
.users_for_channel(event.channel_id);
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
"Reaction",
|
||||
"add",
|
||||
reaction_model_to_response(event.reaction),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
event_bus.on_async::<MessageReactionAddedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
.services
|
||||
.realtime_registry
|
||||
.users_for_channel(event.channel_id);
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
"Reaction",
|
||||
"add",
|
||||
reaction_model_to_response(event.reaction),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<MessageReactionRemovedEvent, _, _>(
|
||||
"message_reaction_removed",
|
||||
move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
.services
|
||||
.realtime_registry
|
||||
.users_for_channel(event.channel_id);
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
"Reaction",
|
||||
"remove",
|
||||
reaction_model_to_response(event.reaction),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
event_bus.on_async::<MessageReactionRemovedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
.services
|
||||
.realtime_registry
|
||||
.users_for_channel(event.channel_id);
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
"Reaction",
|
||||
"remove",
|
||||
reaction_model_to_response(event.reaction),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<ChannelCreatedEvent, _, _>("channel_created", move |event| {
|
||||
event_bus.on_async::<ChannelCreatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
if let Err(error) = router
|
||||
@@ -251,7 +249,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<ChannelUpdatedEvent, _, _>("channel_updated", move |event| {
|
||||
event_bus.on_async::<ChannelUpdatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let mut users = router
|
||||
@@ -274,7 +272,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<ChannelDeletedEvent, _, _>("channel_deleted", move |event| {
|
||||
event_bus.on_async::<ChannelDeletedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router
|
||||
@@ -292,9 +290,10 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<category::Model, _, _>("category_created", move |category| {
|
||||
event_bus.on_async::<CategoryCreatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let category = event.category;
|
||||
let users = router.server_users(category.server_id).await;
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
@@ -306,9 +305,10 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<category::Model, _, _>("category_updated", move |category| {
|
||||
event_bus.on_async::<CategoryUpdatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let category = event.category;
|
||||
let users = router.server_users(category.server_id).await;
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
@@ -320,9 +320,10 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<category::Model, _, _>("category_deleted", move |category| {
|
||||
event_bus.on_async::<CategoryDeletedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let category = event.category;
|
||||
let users = router.server_users(category.server_id).await;
|
||||
router
|
||||
.gateway
|
||||
@@ -331,9 +332,10 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<server::Model, _, _>("server_created", move |server| {
|
||||
event_bus.on_async::<ServerCreatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let server = event.server;
|
||||
let users = router.server_users(server.id).await;
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
@@ -345,9 +347,10 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<server::Model, _, _>("server_updated", move |server| {
|
||||
event_bus.on_async::<ServerUpdatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let server = event.server;
|
||||
let users = router.server_users(server.id).await;
|
||||
router.gateway.send_to_users(
|
||||
users,
|
||||
@@ -359,37 +362,31 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<(server::Model, Vec<Uuid>), _, _>(
|
||||
"server_deleted",
|
||||
move |(server, users)| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
router
|
||||
.gateway
|
||||
.send_to_users(users, "Server", "remove", server.id);
|
||||
}
|
||||
},
|
||||
);
|
||||
event_bus.on_async::<ServerDeletedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
router
|
||||
.gateway
|
||||
.send_to_users(event.audience, "Server", "remove", event.server.id);
|
||||
}
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<ServerTreeInvalidatedEvent, _, _>(
|
||||
"server_tree_invalidated",
|
||||
move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = match event.user_ids {
|
||||
Some(users) => users,
|
||||
None => router.server_users(event.server_id).await,
|
||||
};
|
||||
router
|
||||
.gateway
|
||||
.send_to_users(users, "ServerTree", "refresh", event.server_id);
|
||||
}
|
||||
},
|
||||
);
|
||||
event_bus.on_async::<ServerTreeInvalidatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = match event.user_ids {
|
||||
Some(users) => users,
|
||||
None => router.server_users(event.server_id).await,
|
||||
};
|
||||
router
|
||||
.gateway
|
||||
.send_to_users(users, "ServerTree", "refresh", event.server_id);
|
||||
}
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<EmojiCreatedEvent, _, _>("emoji_created", move |event| {
|
||||
event_bus.on_async::<EmojiCreatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router.emoji_users(event.emoji.server_id).await;
|
||||
@@ -403,7 +400,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<EmojiUpdatedEvent, _, _>("emoji_updated", move |event| {
|
||||
event_bus.on_async::<EmojiUpdatedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let mut users = router.emoji_users(event.emoji.server_id).await;
|
||||
@@ -418,7 +415,7 @@ impl RealtimeRouter {
|
||||
});
|
||||
|
||||
let router = Arc::clone(self);
|
||||
event_bus.on_async::<EmojiDeletedEvent, _, _>("emoji_deleted", move |event| {
|
||||
event_bus.on_async::<EmojiDeletedEvent>(move |event| {
|
||||
let router = Arc::clone(&router);
|
||||
async move {
|
||||
let users = router.emoji_users(event.emoji.server_id).await;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::core::state::AppState;
|
||||
use crate::domain::dto::server::{
|
||||
CreateServerRequest, JoinServerRequest, ServerResponse, ServerRolePermissionResponse,
|
||||
ServerTreeResponse,
|
||||
ServerUserPermissionResponse, SetServerPermissionRequest, UpdateServerRequest,
|
||||
ServerTreeResponse, ServerUserPermissionResponse, SetServerPermissionRequest,
|
||||
UpdateServerRequest,
|
||||
};
|
||||
use crate::http::context::{CurrentUser, Superuser};
|
||||
use crate::http::error::HTTPError;
|
||||
@@ -336,10 +336,6 @@ pub async fn set_user_permission(
|
||||
.server
|
||||
.set_user_permission(server_id, user_id, payload.permissions)
|
||||
.await?;
|
||||
state
|
||||
.event_bus
|
||||
.emit("server_user_permission_updated", (server_id, user_id));
|
||||
|
||||
let permission = state
|
||||
.repositories
|
||||
.server
|
||||
@@ -347,6 +343,13 @@ pub async fn set_user_permission(
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
state.event_bus.emit(
|
||||
crate::domain::events::server_permission::ServerUserPermissionUpdatedEvent {
|
||||
server_id,
|
||||
user_id,
|
||||
permission: Some(permission.clone()),
|
||||
},
|
||||
);
|
||||
Ok(Json(mapper::server_user_permission_to_response(permission)))
|
||||
}
|
||||
|
||||
@@ -386,9 +389,13 @@ pub async fn remove_user_permission(
|
||||
.server
|
||||
.remove_user_permission(server_id, user_id)
|
||||
.await?;
|
||||
state
|
||||
.event_bus
|
||||
.emit("server_user_permission_updated", (server_id, user_id));
|
||||
state.event_bus.emit(
|
||||
crate::domain::events::server_permission::ServerUserPermissionUpdatedEvent {
|
||||
server_id,
|
||||
user_id,
|
||||
permission: None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
@@ -449,10 +456,6 @@ pub async fn set_role_permission(
|
||||
.server
|
||||
.set_role_permission(server_id, role_id, payload.permissions)
|
||||
.await?;
|
||||
state
|
||||
.event_bus
|
||||
.emit("server_role_permission_updated", (role_id, server_id));
|
||||
|
||||
let permission = state
|
||||
.repositories
|
||||
.server
|
||||
@@ -460,6 +463,13 @@ pub async fn set_role_permission(
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
state.event_bus.emit(
|
||||
crate::domain::events::server_permission::ServerRolePermissionUpdatedEvent {
|
||||
server_id,
|
||||
role_id,
|
||||
permission: Some(permission.clone()),
|
||||
},
|
||||
);
|
||||
Ok(Json(mapper::server_role_permission_to_response(permission)))
|
||||
}
|
||||
|
||||
@@ -499,9 +509,13 @@ pub async fn remove_role_permission(
|
||||
.server
|
||||
.remove_role_permission(server_id, role_id)
|
||||
.await?;
|
||||
state
|
||||
.event_bus
|
||||
.emit("server_role_permission_updated", (role_id, server_id));
|
||||
state.event_bus.emit(
|
||||
crate::domain::events::server_permission::ServerRolePermissionUpdatedEvent {
|
||||
server_id,
|
||||
role_id,
|
||||
permission: None,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -28,13 +28,10 @@ pub async fn reorder(
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
state.services.server_order.reorder(payload).await?;
|
||||
state.event_bus.emit(
|
||||
"server_tree_invalidated",
|
||||
ServerTreeInvalidatedEvent {
|
||||
server_id,
|
||||
user_ids: None,
|
||||
},
|
||||
);
|
||||
state.event_bus.emit(ServerTreeInvalidatedEvent {
|
||||
server_id,
|
||||
user_ids: None,
|
||||
});
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use crate::domain::events::category::{
|
||||
CategoryCreatedEvent, CategoryDeletedEvent, CategoryUpdatedEvent,
|
||||
};
|
||||
use crate::models::category;
|
||||
use crate::models::server_item_order::OrderedResourceType;
|
||||
use crate::services::ServicesContext;
|
||||
@@ -49,7 +52,9 @@ impl CategoryService {
|
||||
})
|
||||
.await?;
|
||||
|
||||
event_bus.emit("category_created", cat.clone());
|
||||
event_bus.emit(CategoryCreatedEvent {
|
||||
category: cat.clone(),
|
||||
});
|
||||
|
||||
Ok(cat)
|
||||
}
|
||||
@@ -76,7 +81,9 @@ impl CategoryService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("category_updated", cat.clone());
|
||||
event_bus.emit(CategoryUpdatedEvent {
|
||||
category: cat.clone(),
|
||||
});
|
||||
|
||||
Ok(cat)
|
||||
}
|
||||
@@ -107,7 +114,7 @@ impl CategoryService {
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("category_deleted", existing);
|
||||
event_bus.emit(CategoryDeletedEvent { category: existing });
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
|
||||
+44
-21
@@ -2,6 +2,10 @@ use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
|
||||
use crate::domain::events::channel::{
|
||||
ChannelCreatedEvent, ChannelDeletedEvent, ChannelUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::channel_permission::{
|
||||
ChannelRolePermissionUpdatedEvent, ChannelUserPermissionCreatedEvent,
|
||||
ChannelUserPermissionDeletedEvent,
|
||||
};
|
||||
use crate::models::server_item_order::OrderedResourceType;
|
||||
use crate::models::{channel, role};
|
||||
use crate::permissions::PermissionSet;
|
||||
@@ -91,12 +95,9 @@ impl ChannelService {
|
||||
.await?;
|
||||
|
||||
// Post-commit event emission
|
||||
event_bus.emit(
|
||||
"channel_created",
|
||||
ChannelCreatedEvent {
|
||||
channel: channel.clone(),
|
||||
},
|
||||
);
|
||||
event_bus.emit(ChannelCreatedEvent {
|
||||
channel: channel.clone(),
|
||||
});
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
@@ -141,13 +142,10 @@ impl ChannelService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit(
|
||||
"channel_updated",
|
||||
ChannelUpdatedEvent {
|
||||
previous,
|
||||
channel: channel.clone(),
|
||||
},
|
||||
);
|
||||
event_bus.emit(ChannelUpdatedEvent {
|
||||
previous,
|
||||
channel: channel.clone(),
|
||||
});
|
||||
|
||||
Ok(channel)
|
||||
}
|
||||
@@ -178,7 +176,7 @@ impl ChannelService {
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("channel_deleted", ChannelDeletedEvent { channel: existing });
|
||||
event_bus.emit(ChannelDeletedEvent { channel: existing });
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
@@ -214,12 +212,15 @@ impl ChannelService {
|
||||
.exec(&txn)
|
||||
.await?;
|
||||
|
||||
let permission = crate::models::channel_user_permission::Entity::find()
|
||||
.filter(crate::models::channel_user_permission::Column::ChannelId.eq(channel_id))
|
||||
.filter(crate::models::channel_user_permission::Column::UserId.eq(user_id))
|
||||
.one(&txn)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Permission not found"))?;
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit(
|
||||
"channel_user_permission_created",
|
||||
(channel_id, user_id, permissions),
|
||||
);
|
||||
event_bus.emit(ChannelUserPermissionCreatedEvent { permission });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -234,6 +235,11 @@ impl ChannelService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let existing = crate::models::channel_user_permission::Entity::find()
|
||||
.filter(crate::models::channel_user_permission::Column::ChannelId.eq(channel_id))
|
||||
.filter(crate::models::channel_user_permission::Column::UserId.eq(user_id))
|
||||
.one(&txn)
|
||||
.await?;
|
||||
crate::models::channel_user_permission::Entity::delete_many()
|
||||
.filter(crate::models::channel_user_permission::Column::ChannelId.eq(channel_id))
|
||||
.filter(crate::models::channel_user_permission::Column::UserId.eq(user_id))
|
||||
@@ -242,7 +248,9 @@ impl ChannelService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("channel_user_permission_deleted", (channel_id, user_id));
|
||||
if let Some(permission) = existing {
|
||||
event_bus.emit(ChannelUserPermissionDeletedEvent { permission });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -285,9 +293,19 @@ impl ChannelService {
|
||||
.exec(&txn)
|
||||
.await?;
|
||||
|
||||
let permission = crate::models::channel_role_permission::Entity::find()
|
||||
.filter(crate::models::channel_role_permission::Column::ChannelId.eq(channel_id))
|
||||
.filter(crate::models::channel_role_permission::Column::RoleId.eq(role_id))
|
||||
.one(&txn)
|
||||
.await?;
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("channel_role_permission_updated", (role_id, server_id));
|
||||
event_bus.emit(ChannelRolePermissionUpdatedEvent {
|
||||
channel_id,
|
||||
server_id,
|
||||
role_id,
|
||||
permission,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -318,7 +336,12 @@ impl ChannelService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("channel_role_permission_updated", (role_id, server_id));
|
||||
event_bus.emit(ChannelRolePermissionUpdatedEvent {
|
||||
channel_id,
|
||||
server_id,
|
||||
role_id,
|
||||
permission: None,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+20
-56
@@ -3,7 +3,6 @@ use crate::domain::events::message::{
|
||||
};
|
||||
use crate::models::{attachment, channel, message};
|
||||
use crate::services::ServicesContext;
|
||||
use event_bus::Scope;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, Set, TransactionTrait,
|
||||
};
|
||||
@@ -41,9 +40,6 @@ impl MessageService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
let mut scopes: Vec<Scope> = Vec::new();
|
||||
scopes.push(Scope::uuid("channel", msg.channel_id));
|
||||
|
||||
let server_id: Option<Uuid> = channel::Entity::find_by_id(msg.channel_id)
|
||||
.select_only()
|
||||
.column(channel::Column::ServerId)
|
||||
@@ -52,19 +48,11 @@ impl MessageService {
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
|
||||
event_bus.emit_scoped(
|
||||
"message_created",
|
||||
scopes,
|
||||
MessageCreatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
},
|
||||
);
|
||||
event_bus.emit(MessageCreatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
});
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
@@ -119,19 +107,11 @@ impl MessageService {
|
||||
.one(db)
|
||||
.await?
|
||||
.flatten();
|
||||
let mut scopes = vec![Scope::uuid("channel", msg.channel_id)];
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
event_bus.emit_scoped(
|
||||
"message_created",
|
||||
scopes,
|
||||
MessageCreatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
},
|
||||
);
|
||||
event_bus.emit(MessageCreatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
});
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
@@ -165,19 +145,11 @@ impl MessageService {
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
let mut scopes = vec![Scope::uuid("channel", msg.channel_id)];
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
event_bus.emit_scoped(
|
||||
"message_updated",
|
||||
scopes,
|
||||
MessageUpdatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
},
|
||||
);
|
||||
event_bus.emit(MessageUpdatedEvent {
|
||||
server_id,
|
||||
channel_id: msg.channel_id,
|
||||
message: msg.clone(),
|
||||
});
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
@@ -208,19 +180,11 @@ impl MessageService {
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
let mut scopes = vec![Scope::uuid("channel", existing.channel_id)];
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
event_bus.emit_scoped(
|
||||
"message_deleted",
|
||||
scopes,
|
||||
MessageDeletedEvent {
|
||||
server_id,
|
||||
channel_id: existing.channel_id,
|
||||
message: existing,
|
||||
},
|
||||
);
|
||||
event_bus.emit(MessageDeletedEvent {
|
||||
server_id,
|
||||
channel_id: existing.channel_id,
|
||||
message: existing,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::domain::events::message::{MessageReactionAddedEvent, MessageReactionR
|
||||
use crate::http::error::HTTPError;
|
||||
use crate::models::{emoji, message_reaction};
|
||||
use crate::services::ServicesContext;
|
||||
use event_bus::Scope;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, Set};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@@ -117,19 +116,11 @@ impl MessageReactionService {
|
||||
})
|
||||
.await?;
|
||||
|
||||
let mut scopes = vec![Scope::uuid("channel", message.channel_id)];
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
self.context.event_bus.emit_scoped(
|
||||
"message_reaction_added",
|
||||
scopes,
|
||||
MessageReactionAddedEvent {
|
||||
server_id,
|
||||
channel_id: message.channel_id,
|
||||
reaction: reaction.clone(),
|
||||
},
|
||||
);
|
||||
self.context.event_bus.emit(MessageReactionAddedEvent {
|
||||
server_id,
|
||||
channel_id: message.channel_id,
|
||||
reaction: reaction.clone(),
|
||||
});
|
||||
|
||||
Ok((reaction, true))
|
||||
}
|
||||
@@ -151,19 +142,11 @@ impl MessageReactionService {
|
||||
.await?
|
||||
.ok_or(HTTPError::NotFound)?;
|
||||
|
||||
let mut scopes = vec![Scope::uuid("channel", message.channel_id)];
|
||||
if let Some(server_id) = server_id {
|
||||
scopes.push(Scope::uuid("server", server_id));
|
||||
}
|
||||
self.context.event_bus.emit_scoped(
|
||||
"message_reaction_removed",
|
||||
scopes,
|
||||
MessageReactionRemovedEvent {
|
||||
server_id,
|
||||
channel_id: message.channel_id,
|
||||
reaction: reaction.clone(),
|
||||
},
|
||||
);
|
||||
self.context.event_bus.emit(MessageReactionRemovedEvent {
|
||||
server_id,
|
||||
channel_id: message.channel_id,
|
||||
reaction: reaction.clone(),
|
||||
});
|
||||
|
||||
Ok(reaction)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
use crate::domain::events::channel::{ChannelCreatedEvent, ChannelDeletedEvent};
|
||||
use crate::domain::events::channel_permission::{
|
||||
ChannelRolePermissionUpdatedEvent, ChannelUserPermissionCreatedEvent,
|
||||
ChannelUserPermissionDeletedEvent, ChannelUserPermissionUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::role::{RoleUserCreatedEvent, RoleUserDeletedEvent};
|
||||
use crate::domain::events::server::{
|
||||
ServerCreatedEvent, ServerUserCreatedEvent, ServerUserDeletedEvent,
|
||||
};
|
||||
use crate::domain::events::server_permission::{
|
||||
ServerRolePermissionUpdatedEvent, ServerUserPermissionUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::server_tree::ServerTreeInvalidatedEvent;
|
||||
use crate::models::server;
|
||||
use crate::repositories::Repositories;
|
||||
use crate::services::ServicesContext;
|
||||
use event_bus::EventBus;
|
||||
@@ -60,13 +70,10 @@ pub struct PermissionSyncService {
|
||||
|
||||
impl PermissionSyncService {
|
||||
fn invalidate_tree(event_bus: &Arc<EventBus>, server_id: Uuid, user_ids: Option<Vec<Uuid>>) {
|
||||
event_bus.emit(
|
||||
"server_tree_invalidated",
|
||||
ServerTreeInvalidatedEvent {
|
||||
server_id,
|
||||
user_ids,
|
||||
},
|
||||
);
|
||||
event_bus.emit(ServerTreeInvalidatedEvent {
|
||||
server_id,
|
||||
user_ids,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn new(service_context: Arc<ServicesContext>) -> Self {
|
||||
@@ -83,18 +90,18 @@ impl PermissionSyncService {
|
||||
// Événements Serveur & Membres Serveur
|
||||
// ---------------------------------------------------------------------
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"server_created",
|
||||
event_bus.on_async_with::<ServerCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, server: server::Model| async move {
|
||||
Self::sync_server(repositories, server.id).await;
|
||||
move |repositories, event| async move {
|
||||
Self::sync_server(repositories, event.server.id).await;
|
||||
},
|
||||
);
|
||||
|
||||
event_bus.on_async_with(
|
||||
"server_user_created",
|
||||
event_bus.on_async_with::<ServerUserCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (server_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let server_id = event.server_user.server_id;
|
||||
let user_id = event.server_user.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
@@ -104,10 +111,11 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"server_user_deleted",
|
||||
event_bus.on_async_with::<ServerUserDeletedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (server_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let server_id = event.server_user.server_id;
|
||||
let user_id = event.server_user.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
@@ -121,10 +129,11 @@ impl PermissionSyncService {
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"role_user_created",
|
||||
event_bus.on_async_with::<RoleUserCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (_role_id, user_id, server_id): (Uuid, Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let server_id = event.server_id;
|
||||
let user_id = event.role_user.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
@@ -134,10 +143,11 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"role_user_deleted",
|
||||
event_bus.on_async_with::<RoleUserDeletedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (_role_id, user_id, server_id): (Uuid, Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let server_id = event.server_id;
|
||||
let user_id = event.role_user.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
@@ -151,10 +161,11 @@ impl PermissionSyncService {
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"server_role_permission_updated",
|
||||
event_bus.on_async_with::<ServerRolePermissionUpdatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (role_id, server_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let role_id = event.role_id;
|
||||
let server_id = event.server_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_role_members(repositories, role_id, server_id).await;
|
||||
@@ -164,10 +175,11 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"server_user_permission_updated",
|
||||
event_bus.on_async_with::<ServerUserPermissionUpdatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (server_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let server_id = event.server_id;
|
||||
let user_id = event.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
@@ -181,10 +193,9 @@ impl PermissionSyncService {
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_created",
|
||||
event_bus.on_async_with::<ChannelCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, event: ChannelCreatedEvent| {
|
||||
move |repositories, event| {
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
if let Some(server_id) = event.channel.server_id {
|
||||
@@ -196,10 +207,9 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_deleted",
|
||||
event_bus.on_async_with::<ChannelDeletedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, event: ChannelDeletedEvent| {
|
||||
move |repositories, event| {
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
if let Some(server_id) = event.channel.server_id {
|
||||
@@ -211,10 +221,11 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_role_permission_updated",
|
||||
event_bus.on_async_with::<ChannelRolePermissionUpdatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (role_id, server_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let role_id = event.role_id;
|
||||
let server_id = event.server_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_role_members(repositories, role_id, server_id).await;
|
||||
@@ -224,23 +235,34 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_updated",
|
||||
event_bus.on_async_with::<ChannelUserPermissionUpdatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (server_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let user_id = event.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
Self::invalidate_tree(¬ify, server_id, Some(vec![user_id]));
|
||||
if let Some(channel) = repositories
|
||||
.channel
|
||||
.get_by_id(event.channel_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(server_id) = channel.server_id {
|
||||
Self::sync_user(repositories, user_id, server_id).await;
|
||||
Self::invalidate_tree(¬ify, server_id, Some(vec![user_id]));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_created",
|
||||
event_bus.on_async_with::<ChannelUserPermissionCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (channel_id, user_id, _permissions): (Uuid, Uuid, u64)| {
|
||||
move |repositories, event| {
|
||||
let channel_id = event.permission.channel_id;
|
||||
let user_id = event.permission.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
if let Some(channel) = repositories
|
||||
@@ -260,10 +282,11 @@ impl PermissionSyncService {
|
||||
);
|
||||
|
||||
let notify = event_bus.clone();
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_deleted",
|
||||
event_bus.on_async_with::<ChannelUserPermissionDeletedEvent, _>(
|
||||
repositories,
|
||||
move |repositories, (channel_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let channel_id = event.permission.channel_id;
|
||||
let user_id = event.permission.user_id;
|
||||
let notify = notify.clone();
|
||||
async move {
|
||||
if let Some(channel) = repositories
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use crate::domain::events::channel_permission::{
|
||||
ChannelUserPermissionCreatedEvent, ChannelUserPermissionDeletedEvent,
|
||||
ChannelUserPermissionUpdatedEvent,
|
||||
};
|
||||
use crate::domain::events::server_permission::ServerUserPermissionUpdatedEvent;
|
||||
use crate::models::{channel, channel_user, computed_permission::PermissionScopeType};
|
||||
use crate::permissions::ChannelPermission;
|
||||
use crate::repositories::Repositories;
|
||||
@@ -204,10 +209,10 @@ impl RealtimeRegistry {
|
||||
event_bus: Arc<EventBus>,
|
||||
) {
|
||||
let registry = Arc::clone(self);
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_updated",
|
||||
event_bus.on_async_with::<ChannelUserPermissionUpdatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (_channel_id, user_id, _permissions): (Uuid, Uuid, u64)| {
|
||||
move |repositories, event| {
|
||||
let user_id = event.user_id;
|
||||
let registry = Arc::clone(®istry);
|
||||
async move {
|
||||
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
|
||||
@@ -218,10 +223,10 @@ impl RealtimeRegistry {
|
||||
);
|
||||
|
||||
let registry = Arc::clone(self);
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_created",
|
||||
event_bus.on_async_with::<ChannelUserPermissionCreatedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (_channel_id, user_id, _permissions): (Uuid, Uuid, u64)| {
|
||||
move |repositories, event| {
|
||||
let user_id = event.permission.user_id;
|
||||
let registry = Arc::clone(®istry);
|
||||
async move {
|
||||
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
|
||||
@@ -232,10 +237,10 @@ impl RealtimeRegistry {
|
||||
);
|
||||
|
||||
let registry = Arc::clone(self);
|
||||
event_bus.on_async_with(
|
||||
"channel_user_permission_deleted",
|
||||
event_bus.on_async_with::<ChannelUserPermissionDeletedEvent, _>(
|
||||
repositories.clone(),
|
||||
move |repositories, (_channel_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let user_id = event.permission.user_id;
|
||||
let registry = Arc::clone(®istry);
|
||||
async move {
|
||||
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
|
||||
@@ -247,10 +252,10 @@ impl RealtimeRegistry {
|
||||
|
||||
let registry = Arc::clone(self);
|
||||
let repositories = repositories.clone();
|
||||
event_bus.on_async_with(
|
||||
"server_user_permission_updated",
|
||||
event_bus.on_async_with::<ServerUserPermissionUpdatedEvent, _>(
|
||||
repositories,
|
||||
move |repositories, (_server_id, user_id): (Uuid, Uuid)| {
|
||||
move |repositories, event| {
|
||||
let user_id = event.user_id;
|
||||
let registry = Arc::clone(®istry);
|
||||
async move {
|
||||
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
|
||||
|
||||
+26
-12
@@ -1,4 +1,8 @@
|
||||
use crate::models::role;
|
||||
use crate::domain::events::role::{
|
||||
RoleCreatedEvent, RoleDeletedEvent, RoleUpdatedEvent, RoleUserCreatedEvent,
|
||||
RoleUserDeletedEvent,
|
||||
};
|
||||
use crate::models::{role, role_user};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{ActiveModelTrait, EntityTrait, TransactionTrait};
|
||||
use std::sync::Arc;
|
||||
@@ -27,7 +31,7 @@ impl RoleService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("role_created", role.clone());
|
||||
event_bus.emit(RoleCreatedEvent { role: role.clone() });
|
||||
|
||||
Ok(role)
|
||||
}
|
||||
@@ -45,7 +49,7 @@ impl RoleService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("role_updated", role.clone());
|
||||
event_bus.emit(RoleUpdatedEvent { role: role.clone() });
|
||||
|
||||
Ok(role)
|
||||
}
|
||||
@@ -56,14 +60,15 @@ impl RoleService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let existing = role::Entity::find_by_id(id).one(&txn).await?;
|
||||
let res = role::Entity::delete_by_id(id).exec(&txn).await?;
|
||||
|
||||
let deleted = res.rows_affected > 0;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("role_deleted", id);
|
||||
if let Some(role) = existing.filter(|_| deleted) {
|
||||
event_bus.emit(RoleDeletedEvent { role });
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
@@ -82,9 +87,14 @@ impl RoleService {
|
||||
.add_member(role_id, user_id)
|
||||
.await?;
|
||||
if added {
|
||||
self.service_context
|
||||
.event_bus
|
||||
.emit("role_user_created", (role_id, user_id, server_id));
|
||||
let role_user = role_user::Entity::find_by_id((role_id, user_id))
|
||||
.one(&self.service_context.repositories.server.context.db)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("Role membership not found"))?;
|
||||
self.service_context.event_bus.emit(RoleUserCreatedEvent {
|
||||
role_user,
|
||||
server_id,
|
||||
});
|
||||
}
|
||||
Ok(added)
|
||||
}
|
||||
@@ -95,16 +105,20 @@ impl RoleService {
|
||||
user_id: Uuid,
|
||||
server_id: Uuid,
|
||||
) -> Result<bool, anyhow::Error> {
|
||||
let existing = role_user::Entity::find_by_id((role_id, user_id))
|
||||
.one(&self.service_context.repositories.server.context.db)
|
||||
.await?;
|
||||
let removed = self
|
||||
.service_context
|
||||
.repositories
|
||||
.role
|
||||
.remove_member(role_id, user_id)
|
||||
.await?;
|
||||
if removed {
|
||||
self.service_context
|
||||
.event_bus
|
||||
.emit("role_user_deleted", (role_id, user_id, server_id));
|
||||
if let Some(role_user) = existing.filter(|_| removed) {
|
||||
self.service_context.event_bus.emit(RoleUserDeletedEvent {
|
||||
role_user,
|
||||
server_id,
|
||||
});
|
||||
}
|
||||
Ok(removed)
|
||||
}
|
||||
|
||||
+29
-9
@@ -1,3 +1,7 @@
|
||||
use crate::domain::events::server::{
|
||||
ServerCreatedEvent, ServerDeletedEvent, ServerUpdatedEvent, ServerUserCreatedEvent,
|
||||
ServerUserDeletedEvent,
|
||||
};
|
||||
use crate::models::{role, role_user, server, server_user};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
@@ -63,7 +67,9 @@ impl ServerService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("server_created", srv.clone());
|
||||
event_bus.emit(ServerCreatedEvent {
|
||||
server: srv.clone(),
|
||||
});
|
||||
|
||||
Ok(srv)
|
||||
}
|
||||
@@ -97,7 +103,7 @@ impl ServerService {
|
||||
return Ok(srv);
|
||||
}
|
||||
|
||||
server_user::ActiveModel {
|
||||
let membership = server_user::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
user_id: Set(user_id),
|
||||
..Default::default()
|
||||
@@ -119,7 +125,9 @@ impl ServerService {
|
||||
.await?;
|
||||
|
||||
txn.commit().await?;
|
||||
event_bus.emit("server_user_created", (server_id, user_id));
|
||||
event_bus.emit(ServerUserCreatedEvent {
|
||||
server_user: membership,
|
||||
});
|
||||
self.service_context
|
||||
.repositories
|
||||
.read_state
|
||||
@@ -152,7 +160,9 @@ impl ServerService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("server_updated", srv.clone());
|
||||
event_bus.emit(ServerUpdatedEvent {
|
||||
server: srv.clone(),
|
||||
});
|
||||
|
||||
Ok(srv)
|
||||
}
|
||||
@@ -182,7 +192,10 @@ impl ServerService {
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("server_deleted", (existing, audience));
|
||||
event_bus.emit(ServerDeletedEvent {
|
||||
server: existing,
|
||||
audience,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
@@ -194,7 +207,7 @@ impl ServerService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
crate::models::server_user::ActiveModel {
|
||||
let membership = crate::models::server_user::ActiveModel {
|
||||
server_id: Set(server_id),
|
||||
user_id: Set(user_id),
|
||||
..Default::default()
|
||||
@@ -214,7 +227,9 @@ impl ServerService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("server_user_created", (server_id, user_id));
|
||||
event_bus.emit(ServerUserCreatedEvent {
|
||||
server_user: membership,
|
||||
});
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
@@ -225,6 +240,11 @@ impl ServerService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let membership = server_user::Entity::find()
|
||||
.filter(server_user::Column::ServerId.eq(server_id))
|
||||
.filter(server_user::Column::UserId.eq(user_id))
|
||||
.one(&txn)
|
||||
.await?;
|
||||
let res = crate::models::server_user::Entity::delete_many()
|
||||
.filter(crate::models::server_user::Column::ServerId.eq(server_id))
|
||||
.filter(crate::models::server_user::Column::UserId.eq(user_id))
|
||||
@@ -235,8 +255,8 @@ impl ServerService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("server_user_deleted", (server_id, user_id));
|
||||
if let Some(server_user) = membership.filter(|_| deleted) {
|
||||
event_bus.emit(ServerUserDeletedEvent { server_user });
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::auth::password;
|
||||
use crate::domain::events::user::{
|
||||
UserChangedEvent, UserCreatedEvent, UserDeletedEvent, UserUpdatedEvent,
|
||||
};
|
||||
use crate::models::{role, user};
|
||||
use crate::services::ServicesContext;
|
||||
use sea_orm::{
|
||||
@@ -30,7 +33,7 @@ impl UserService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("user_created", usr.clone());
|
||||
event_bus.emit(UserCreatedEvent { user: usr.clone() });
|
||||
|
||||
Ok(usr)
|
||||
}
|
||||
@@ -48,7 +51,7 @@ impl UserService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("user_updated", usr.clone());
|
||||
event_bus.emit(UserUpdatedEvent { user: usr.clone() });
|
||||
|
||||
Ok(usr)
|
||||
}
|
||||
@@ -79,7 +82,7 @@ impl UserService {
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
event_bus.emit("user_changed", usr);
|
||||
event_bus.emit(UserChangedEvent { user: usr });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -90,14 +93,15 @@ impl UserService {
|
||||
|
||||
let txn = db.begin().await?;
|
||||
|
||||
let existing = user::Entity::find_by_id(id).one(&txn).await?;
|
||||
let res = user::Entity::delete_by_id(id).exec(&txn).await?;
|
||||
|
||||
let deleted = res.rows_affected > 0;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
if deleted {
|
||||
event_bus.emit("user_deleted", id);
|
||||
if let Some(user) = existing.filter(|_| deleted) {
|
||||
event_bus.emit(UserDeletedEvent { user });
|
||||
}
|
||||
|
||||
Ok(deleted)
|
||||
|
||||
Reference in New Issue
Block a user