use std::collections::HashMap;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use std::thread;
use crate::message_bus::{Envelope, Message, PublishHook, NoOpHook};
pub trait Subscriber: Send + 'static {
fn receive(&mut self, msg: Box<dyn Message>, at: std::time::SystemTime) -> Vec<Envelope>;
fn tick(&mut self, at: std::time::SystemTime) -> Vec<Envelope>;
}
struct NopEnvelope;
impl Message for NopEnvelope {}
pub struct MessageBus<H: PublishHook = NoOpHook> {
subscribers: HashMap<String, Box<dyn Subscriber>>,
msg_rxs: Option<Vec<flume::Receiver<Envelope>>>,
msg_txs: Vec<flume::Sender<Envelope>>,
tick_interval: std::time::Duration,
shutdown: Arc<AtomicBool>,
handle: Option<thread::JoinHandle<()>>,
hook: Option<H>,
}
impl MessageBus<NoOpHook> {
pub fn new(tick_interval: std::time::Duration, queues: usize) -> Self {
Self::with_hook(tick_interval, queues, NoOpHook)
}
}
impl<H: PublishHook> MessageBus<H> {
pub fn with_hook(tick_interval: std::time::Duration, queues: usize, hook: H) -> Self {
let mut queues = queues;
if queues == 0 {
queues = 1;
}
let (msg_txs, msg_rxs): (Vec<_>, Vec<_>) = (0..queues).map(|_| flume::unbounded()).unzip();
Self {
subscribers: HashMap::new(),
msg_rxs: Some(msg_rxs),
msg_txs,
tick_interval,
shutdown: Arc::new(AtomicBool::new(false)),
handle: None,
hook: Some(hook),
}
}
pub fn start(&mut self) -> Vec<flume::Sender<Envelope>> {
println!("Starting MessageBus");
let rx = self.msg_rxs.take().expect("MessageBus already started");
let tx = self.msg_txs.clone();
let tick_interval = self.tick_interval;
let shutdown = self.shutdown.clone();
let subscribers = std::mem::take(&mut self.subscribers);
let hook = self.hook.take().expect("MessageBus already started");
let handle = thread::spawn(move || {
Self::process_messages(rx, tx, subscribers, tick_interval, shutdown, hook);
});
self.handle = Some(handle);
self.msg_txs.clone()
}
pub fn subscribe(&mut self, destination: String, sender: Box<dyn Subscriber>) {
self.subscribers.insert(destination, sender);
}
pub fn publish(&mut self, envelope: Envelope) {
let priority = envelope.priority.min(self.msg_txs.len() - 1);
self.msg_txs[priority].send(envelope).unwrap();
}
fn process_messages(
rxs: Vec<flume::Receiver<Envelope>>,
txs: Vec<flume::Sender<Envelope>>,
mut subscribers: HashMap<String, Box<dyn Subscriber>>,
tick_interval: std::time::Duration,
shutdown: Arc<AtomicBool>,
hook: H,
) {
println!("Processing messages");
let start_time = std::time::SystemTime::now();
let mut next_tick = start_time + tick_interval;
for subscriber in subscribers.values_mut() {
let envelopes = subscriber.tick(start_time);
for envelope in envelopes {
hook.on_publish(&envelope, start_time);
let priority = envelope.priority.min(txs.len() - 1);
txs[priority].send(envelope).unwrap();
}
}
loop {
println!("Processing messages loop");
if shutdown.load(Ordering::SeqCst) {
break;
}
let now = std::time::SystemTime::now();
if now >= next_tick {
while next_tick <= std::time::SystemTime::now() {
let at = next_tick;
for (name, subscriber) in subscribers.iter_mut() {
println!("Ticking {}", name);
let envelopes = subscriber.tick(at);
for envelope in envelopes {
hook.on_publish(&envelope, at);
let priority = envelope.priority.min(txs.len() - 1);
txs[priority].send(envelope).unwrap();
}
}
next_tick += tick_interval;
}
continue;
}
let timeout = next_tick.duration_since(now).unwrap_or(tick_interval);
let mut envelope_opt = None;
for i in (0..rxs.len()).rev() {
if let Ok(envelope) = rxs[i].try_recv() {
envelope_opt = Some(envelope);
break;
}
}
if envelope_opt.is_none() {
let mut selector = flume::Selector::new();
for rx in &rxs {
selector = selector.recv(rx, |result| result);
}
match selector.wait_timeout(timeout) {
Ok(Ok(envelope)) => envelope_opt = Some(envelope),
Ok(Err(_)) => break, Err(_) => continue, }
}
if let Some(envelope) = envelope_opt {
let Some(subscriber) = subscribers.get_mut(&envelope.destination) else {
continue;
};
let at = std::time::SystemTime::now();
let envelopes = subscriber.receive(envelope.message, at);
for envelope in envelopes {
hook.on_publish(&envelope, at);
let priority = envelope.priority.min(txs.len() - 1);
txs[priority].send(envelope).unwrap();
}
}
}
}
pub fn stop(&mut self) {
if self.shutdown.swap(true, Ordering::SeqCst) {
return;
}
let _ = self.msg_txs[self.msg_txs.len() - 1].send(Envelope {
message: Box::new(NopEnvelope),
destination: "".to_string(),
priority: 0,
});
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
impl<H: PublishHook> Drop for MessageBus<H> {
fn drop(&mut self) {
self.stop();
}
}