barker 0.1.0

A small, synchronous trait-object event bus with type-safe handlers, TTL, and priority metadata.
Documentation
[![CI](https://github.com/AnthonyUtt/barker/actions/workflows/ci.yml/badge.svg)](https://github.com/AnthonyUtt/barker/actions/workflows/ci.yml)

# barker

A small, synchronous, trait-object event bus for Rust — type-safe handlers, TTL, and a static global for low-ceremony use.

## Usage

```rust
use std::any::{Any, TypeId};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use barker::{Message, MessageBus, MessageHandler};

// 1. Any '`static + Send + Sync + Debug` type can be a message.
#[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. Handlers see `&dyn Message`; downcast to the concrete type to read fields.
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);
        }
    }
}

fn main() {
    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, crowd")).unwrap();
    bus.process_messages(None).unwrap();

    assert_eq!(count.load(Ordering::SeqCst), 1);
}
```

Prefer a process-wide bus? `barker::send`, `barker::register_handler`, and `barker::process_messages` are free-function wrappers around `MessageBus::global()`.

## Design overview

- **Trait-based messages.** Any `'static + Send + Sync + Debug` type implementing [`Message`]https://docs.rs/barker/latest/barker/trait.Message.html can flow through the bus. There is no central `enum`, so downstream crates extend the message vocabulary without modifying barker.
- **Filtering: typed vs generic handlers.** Registering a handler with `Some(TypeId::of::<T>())` makes it fire only for messages of type `T`. Registering with `None` makes it a generic handler that fires for every message. Matching uses [`std::any::TypeId`]https://doc.rust-lang.org/std/any/struct.TypeId.html.
- **Buffered send, explicit drain.** `send` enqueues onto an internal [`flume`]https://crates.io/crates/flume channel and returns. Handlers do not run until `process_messages` is called — typically once per frame, per tick, or at some other coarse cadence your application controls.
- **TTL enforcement.** A message can return `Some(Duration)` from `Message::ttl()`; if the drain encounters it past its expiry, it is silently skipped.
- **Registration-order dispatch.** Within a single drain, handlers fire in the order they were registered. Fan-out (multiple handlers for the same `TypeId`) is supported and each handler runs to completion before the next.

### Aspirational metadata

The `Message` trait declares `priority()` and `requires_ack()`, but the drain does not currently consult either:

- `priority()` is **not** used to reorder dispatch. Handlers fire in registration order regardless of the priorities of pending messages.
- `requires_ack()` has no acknowledgement plumbing. Senders cannot observe whether a message was received.

These methods are preserved on the trait for forward compatibility; document your own conventions per-message, but do not rely on the bus to honour them.

## Origin

Extracted from the [VITRIOL game engine](https://github.com/AnthonyUtt/vitriol), where the bus decouples input, window, and system events across the plugin architecture.

## License

Dual-licensed under either of [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.