#bus #message #async #module #run-time #event-handling

nightly dabus

Dynamic Aplication Controll Bus - an asynchronous, event based module system for thoes who want to move fast and *not* break things

5 releases

0.5.1 Sep 20, 2023
0.5.0 Jun 8, 2022
0.4.3 May 27, 2022
0.4.2 May 26, 2022
0.4.1 May 26, 2022

#547 in Asynchronous

MIT license

54KB
1K SLoC

DABus

DABus is a multi-type aplication bus. It allows for you to have multiple completely independant "Handlers" or "Bus Stops" that you can interact with and can interact with eachother without aknowlaging eachothers existance. it maintains all of rust's type saftey and guarentees, while being able to act in a highly dynamic fasion, almost like something out of javascript, but with none of the downsides.

Issue tracker

the issue tracker for this project is on the github mirror

Key Features

  • Type-Erased: the central DABus structure does not need to know any of the types related to a handler, or any events it is processing
  • Asynchronous: all handlers are async
  • Thread-Safe: multithreaded async executers are fully supported
  • Type-Safe: handlers and event calls are fully statically typed
  • Convenient: API does not force you to go through inconvenient loopholes

Limitations

  • As preivously mentioned, it is asynchronous and thread-safe. unfortunatally, there is no way around this, as all types must be Sync and Send, and async is a core requirement of how the executor functions
  • Because of all of the dynamic typing used internally, this relies heavliy on dynamic dispatch and thus suffers from its performance issues (dont worry, its not slow)
  • For debugging, this implements logging using the log crate, but it is still rather confusing to debug. hopefully this will change soon with backtraces

Usage

A event handler for DABus is a simple struct method, something like this:

use dabus::BusInterface;

#[derive(Debug)]
struct ExampleHandler;

impl ExampleHandler {
    async fn hello_world(&mut self, arguments: (), mut _interface: BusInterface) {
        /*
        here, arguments is the args passed to the event call,
        and _interface is a struct for communicating with the bus that invoked it

        warning! do NOT use BusInterface outside of the async handler it was passed to!
        it may seem like a good way of doing things, but IT WILL NOT WORK!!!
        */
        println!("Hello, World!");
    }
}

and then define the event it goes along with

//            the name     args  return type
dabus::event!(HELLO_EVENT, (),   ());

To convert this from a regular struct to an bus stop, implement BusStop

use dabus::{BusStop, EventRegister};

impl BusStop for HelloHandler {
    // this function provides a list of the handlers that this stop provides
    fn registered_handlers(h: EventRegister<Self>) -> EventRegister<Self> {
        //        event def    event function
        h.handler(HELLO_EVENT, Self::hello_world)
    }
}

and finally, to use this

use dabus::DABus;

#[tokio::main]
async fn main() {
    let mut bus = DABus::new();
    // create a new instance of HelloHandler, and pass it to the bus for useage
    bus.register(HelloHandler);
    //      the event     arguments (type from event def)
    bus.fire(PRINT_EVENT, "Hello, World!".to_string()).await.unwrap();
    // you should now see Hello, World! on your terminal!
}

Important Note

This crate requires nightly!

this is why:

#![feature(downcast_unchecked)]
#![feature(drain_filter)]
#![allow(incomplete_features)]
#![feature(specialization)]

Crate Features

name description default behavior
backtrace_track_values backtraces will include debug-formats of handler arguments and returns disabled

TODO's

  • docs
  • tests
  • backtraces (do LATER, do logging NOW)
  • format backtraces
  • a way of turning off backtraces? (performance)
  • examples IMPORTANT
  • proper error handling
  • multi-handler events
  • more complex event matching (allow handlers to consume an event, after looking at the arguments?)
  • nested handler calls
  • error forwarding
  • take a look at rust api guidelines
  • profiling and optimization

Dependencies

~1.2–2MB
~39K SLoC