Expand description
barker is a small, synchronous, trait-object event bus.
Think of it as a town crier: anyone can shout a message into the crowd; some listeners stop because the message is addressed to their type, others (generic handlers) listen to everything that flies by, and the rest walk on.
§Quick start
use std::any::{Any, TypeId};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use barker::{Message, MessageHandler, MessageBus};
// 1. Define a message. Any `'static + Send + Sync + Debug` type works.
#[derive(Debug)]
struct Ping(&'static str);
impl Message for Ping {
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
}
// 2. Define a handler. Use `as_any().downcast_ref::<T>()` to recover the concrete
// message type. Share mutable state via Arc + atomics or Mutex.
struct Counter(Arc<AtomicUsize>);
impl MessageHandler for Counter {
fn call(&self, msg: &dyn Message) {
if msg.as_any().downcast_ref::<Ping>().is_some() {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
}
// 3. Wire it up on an owned bus instance (or use the process-wide global — see below).
let bus = MessageBus::new();
let count = Arc::new(AtomicUsize::new(0));
bus.register_handler(
Box::new(Counter(count.clone())),
Some(TypeId::of::<Ping>()),
).unwrap();
bus.send(Ping("hello")).unwrap();
bus.process_messages(None).unwrap();
assert_eq!(count.load(Ordering::SeqCst), 1);§How it works
- Trait-based messages. Anything implementing
Messagecan flow through the bus. There is no centralenum— downstream crates can define their own message types without modifying barker. - Filtering. Handlers register with either
Some(TypeId::of::<T>())(typed — fires only for messages of typeT) orNone(generic — fires for every message). Matching usesTypeId, so a typed handler never sees messages of an unrelated type even when both share a category. - Buffered send, explicit drain.
MessageBus::sendenqueues onto an internalflumechannel and returns immediately. Handlers do not run until someone callsMessageBus::process_messages. - TTL enforcement. Messages whose
Message::ttlhas elapsed by drain time are silently skipped. - Registration-order dispatch. Within a drain, handlers are invoked in the order they were registered. There is no priority-based reordering.
§Static global vs owned instance
For low-ceremony, application-wide event flows, use the free functions send,
register_handler, and process_messages — they all delegate to a process-wide
MessageBus accessible via MessageBus::global. For tests, plugins with isolated
event streams, or library code that should not touch global state, construct your own
bus with MessageBus::new or MessageBus::bounded.
§Aspirational metadata
The Message trait declares priority and
requires_ack, but neither is currently consulted by the
drain. They are preserved on the trait for forward compatibility; document your
intent on a per-message basis, but do not rely on the bus to honour them.
§Origin
Extracted from the VITRIOL game engine, where the bus is used to decouple input, window, and system events across the plugin architecture.
Structs§
- Message
Bus - A buffered, synchronous event bus that routes
Messagevalues to registeredMessageHandlers.
Enums§
- Barker
Error - Errors returned by
MessageBusoperations.
Traits§
- Message
- Any value that can flow through a
MessageBus. - Message
Handler - A subscriber that reacts to messages drained from a
MessageBus.
Functions§
- process_
messages - Drain up to
limitmessages from the process-wideMessageBus::globaland dispatch them. - register_
handler - Register
handleron the process-wideMessageBus::global. - send
- Enqueue
msgon the process-wideMessageBus::global.
Type Aliases§
- Result
- Convenience alias for
std::result::Resultspecialised toBarkerError.