dabus 0.5.0

Dynamic Aplication Controll Bus - an asynchronous, event based module system for thoes who want to move fast and *not* break things
Documentation
use std::any::TypeId;

use flume::Sender;

use crate::{
    bus::error::{CallEvent, CallTrace}, core::dyn_var::DynVar, util::dyn_debug::DynDebug, EventDef,
};

#[derive(Debug)]
pub enum BusInterfaceEvent {
    Fire {
        def: TypeId,
        args: DynVar,
        responder: Sender<Result<DynVar, CallTrace>>,
        trace_data: CallEvent,
    },
    FwdBusError {
        error: CallTrace,
        blocker: Sender<()>,
    },
}

/// Provides a limited [`DABus`] like api for handler implementations.
///
/// This is passed to handlers, giving them a way of running actions on the bus that they are being run from.
///
/// [`DABus`]: crate::bus::DABus
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct BusInterface {
    pub(crate) channel: Sender<BusInterfaceEvent>,
}

impl BusInterface {
    pub(crate) const fn new(sender: Sender<BusInterfaceEvent>) -> Self {
        Self { channel: sender }
    }

    /// Fires an event on the bus, running appropreate handlers and returning the result.
    /// This function is similar to [`DABus::fire`] in the sense that from the outside, it behaves the same
    /// however internally it does not. see the `Notes` section for more details
    ///
    /// # Returns
    ///
    /// on success, this returns the return value sent by the handler, as well as a call trace (this will change)
    ///
    /// on failure, this returns only the call trace, which can be used to find what went wrong
    ///
    /// # Panics
    ///
    /// if a handler that is called panics (or the runtime is broken)
    ///
    /// # Errors
    ///
    /// if there is some (expected) error with the runtime. currently this only includes not finding an appropreate handler
    ///
    /// # Notes
    /// like all functions on this struct, this does not execute an event iself but rather forwards it to the current runtime.
    /// this means that if useing this after the scope of the handler it was given to has ended should be considered **Undefined Behavior** (eventually there will be some safeguard to fix this)
    ///
    /// [`DABus::fire`]: crate::bus::DABus::fire
    pub async fn fire<
        Tag: unique_type::Unique,
        At: DynDebug + Sync + Send + 'static,
        Rt: DynDebug + Sync + Send + 'static,
    >(
        &mut self,
        def: &'static EventDef<Tag, At, Rt>,
        args: At,
    ) -> Result<Rt, CallTrace> {
        let trace_data = CallEvent::from_event_def(def, &args);
        let _ = def;
        let def = TypeId::of::<Tag>();
        let args = DynVar::new(args);
        let (responder, response) = flume::bounded::<Result<DynVar, CallTrace>>(1);
        self.channel
            .send(BusInterfaceEvent::Fire {
                def,
                args,
                responder,
                trace_data,
            })
            .unwrap();
        Ok(response
            .into_recv_async()
            .await
            .unwrap()?
            .try_to::<Rt>()
            .unwrap())
    }

    /// takes a error (from a nested call, presumablely) and forwards it to the caller of the current event (via the runtime and a deal with the devil)
    ///
    /// this is a easy way to handle errors, as it will forward the error, and can produce nice backtraces
    ///
    /// # Panics
    ///
    /// it shouldent, unless something is horribly wrong with the library
    ///
    /// # Footguns
    ///
    /// - this function (from the perspective of the handler) will never return, but from the persepective of the program it will, so keep that in mind.
    ///
    /// - see the `Notes` section in [`BusInterface::fire`]
    pub async fn fwd_bus_err(
        &self,
        error: CallTrace,
    ) -> ! {
        let (blocker, blocks) = flume::bounded::<()>(1);
        self.channel
            .send(BusInterfaceEvent::FwdBusError { error, blocker })
            .unwrap();
        blocks.recv_async().await.unwrap();
        unreachable!()
    }
}

/// Utility for handling bus errors inside of bus handlers
#[async_trait]
pub trait BusErrorUtil<T> {
    /// unwraps an `Result`, or forwards the error to [`BusInterface::fwd_bus_err`].
    ///
    /// see [`BusInterface::fwd_bus_err`] for more information
    async fn unwrap_or_fwd(self, bus: &BusInterface) -> T;
}

#[async_trait]
impl<T: Send> BusErrorUtil<T> for Result<T, CallTrace> {
    /// unwraps an `Result`, or forwards the error to [`BusInterface::fwd_bus_err`].
    ///
    /// see [`BusInterface::fwd_bus_err`] for more information
    async fn unwrap_or_fwd(self, bus: &BusInterface) -> T {
        match self {
            Ok(x) => x,
            Err(err) => bus.fwd_bus_err(err).await,
        }
    }
}