Skip to main content

Crate barker

Crate barker 

Source
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 Message can flow through the bus. There is no central enum — 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 type T) or None (generic — fires for every message). Matching uses TypeId, so a typed handler never sees messages of an unrelated type even when both share a category.
  • Buffered send, explicit drain. MessageBus::send enqueues onto an internal flume channel and returns immediately. Handlers do not run until someone calls MessageBus::process_messages.
  • TTL enforcement. Messages whose Message::ttl has 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§

MessageBus
A buffered, synchronous event bus that routes Message values to registered MessageHandlers.

Enums§

BarkerError
Errors returned by MessageBus operations.

Traits§

Message
Any value that can flow through a MessageBus.
MessageHandler
A subscriber that reacts to messages drained from a MessageBus.

Functions§

process_messages
Drain up to limit messages from the process-wide MessageBus::global and dispatch them.
register_handler
Register handler on the process-wide MessageBus::global.
send
Enqueue msg on the process-wide MessageBus::global.

Type Aliases§

Result
Convenience alias for std::result::Result specialised to BarkerError.