rusty_junctions 0.1.0

Join Pattern implementation in Rust.
Documentation
//! Collection of types to increase readability and maintainability of the
//! crate.

use std::any::Any;
use std::sync::mpsc::Sender;
use std::thread::{JoinHandle, Thread};

use crate::patterns;

/// Shallow wrapper for a trait object using `Box` that can pass through thread
/// boundaries.
pub struct Message(Box<dyn Any + Send>);

impl Message {
    pub(crate) fn new<T>(raw_value: T) -> Message
    where
        T: Any + Send,
    {
        Message(Box::new(raw_value))
    }

    /// Cast internal trait object to `Box<T>`.
    pub(crate) fn downcast<T>(self) -> Result<Box<T>, Box<dyn Any + Send>>
    where
        T: Any + Send,
    {
        self.0.downcast::<T>()
    }
}

/// Standardized packet to be used to send messages of various types on the
/// channels of a Junction.
pub enum Packet {
    /// General message send from channel identified by `channel_id`.
    Message {
        channel_id: ids::ChannelId,
        msg: Message,
    },
    /// Request a new channel ID from the Junction so a new channel can be
    /// constructed. New ID will be sent back through `return_sender`.
    NewChannelIdRequest {
        return_sender: Sender<ids::ChannelId>,
    },
    /// Request adding a new Join Pattern to the Junction.
    AddJoinPatternRequest { join_pattern: JoinPattern },
    /// Request the internal control thread managing the `Message`s to shut down.
    ShutDownRequest,
}

/// Enum defining all Join Patterns that can be added to a Junction using the
/// `AddJoinPatternRequest` in a `Packet`.
pub enum JoinPattern {
    /// Single channel Join Pattern.
    UnarySend(patterns::unary::SendJoinPattern),
    /// Single `RecvChannel` Join Pattern.
    UnaryRecv(patterns::unary::RecvJoinPattern),
    /// Single `BidirChannel` Join Pattern.
    UnaryBidir(patterns::unary::BidirJoinPattern),
    /// Two `SendChannel` Join Pattern.
    BinarySend(patterns::binary::SendJoinPattern),
    /// `SendChannel` and `RecvChannel` Join Pattern.
    BinaryRecv(patterns::binary::RecvJoinPattern),
    /// `SendChannel` and `BidirChannel` Join Pattern.
    BinaryBidir(patterns::binary::BidirJoinPattern),
    /// Three `SendChannel` Join Pattern.
    TernarySend(patterns::ternary::SendJoinPattern),
    /// Two `SendChannel` and `RecvChannel` Join Pattern.
    TernaryRecv(patterns::ternary::RecvJoinPattern),
    /// Two `SendChannel` and `BidirChannel` Join Pattern.
    TernaryBidir(patterns::ternary::BidirJoinPattern),
}

/// Handle to a `Junction`'s underlying `Controller`.
///
/// This struct carries a `JoinHandle` to the thread that the `Controller` of
/// a `Junction` is running in. It allows for the `Controller` and its thread
/// to be stopped gracefully at any point.
pub struct ControllerHandle {
    sender: Sender<Packet>,
    control_thread_handle: Option<JoinHandle<()>>,
}

impl ControllerHandle {
    pub(crate) fn new(sender: Sender<Packet>, handle: JoinHandle<()>) -> ControllerHandle {
        ControllerHandle {
            sender,
            control_thread_handle: Some(handle),
        }
    }

    /// Extracts a handle to the underlying thread.
    pub fn thread(&self) -> Option<&Thread> {
        match &self.control_thread_handle {
            Some(h) => Some(h.thread()),
            None => None,
        }
    }

    /// Request the `Controller` to stop gracefully, then join its thread.
    ///
    /// # Panics
    ///
    /// Panics if it was unable to send shut-down request to the control thread.
    pub fn stop(&mut self) {
        self.sender.send(Packet::ShutDownRequest).unwrap();

        self.control_thread_handle.take().unwrap().join().unwrap();
    }
}

/// Function types related to various kind of functions that can be stored and
/// executed with Join Patterns.
pub mod functions {
    use super::*;

    /// Types and Traits for functions which take one argument.
    pub mod unary {
        use super::*;

        /// Trait to allow boxed up functions that take one `Message` and return
        /// nothing to be cloned.
        pub trait FnBoxClone: Fn(Message) -> () + Send {
            fn clone_box(&self) -> Box<dyn FnBoxClone>;
        }

        impl<F> FnBoxClone for F
        where
            F: Fn(Message) -> () + Send + Clone + 'static,
        {
            /// Proxy function to be able to implement the `Clone` trait on
            /// boxed up functions that take one `Message` and return nothing.
            fn clone_box(&self) -> Box<dyn FnBoxClone> {
                Box::new(self.clone())
            }
        }

        impl Clone for Box<dyn FnBoxClone> {
            fn clone(&self) -> Box<dyn FnBoxClone> {
                (**self).clone_box()
            }
        }

        /// Type alias for boxed up cloneable functions that take one `Message` and
        /// return nothing. Mainly meant to increase readability of code.
        pub type FnBox = Box<dyn FnBoxClone>;
    }

    /// Types and Traits for functions which take two arguments.
    pub mod binary {
        use super::*;

        /// Trait to allow boxed up functions that take two `Message`s and return
        /// nothing to be cloned.
        pub trait FnBoxClone: Fn(Message, Message) -> () + Send {
            fn clone_box(&self) -> Box<dyn FnBoxClone>;
        }

        impl<F> FnBoxClone for F
        where
            F: Fn(Message, Message) -> () + Send + Clone + 'static,
        {
            /// Proxy function to be able to implement the `Clone` trait on
            /// boxed up functions that take two `Message`s and return nothing.
            fn clone_box(&self) -> Box<dyn FnBoxClone> {
                Box::new(self.clone())
            }
        }

        impl Clone for Box<dyn FnBoxClone> {
            fn clone(&self) -> Box<dyn FnBoxClone> {
                (**self).clone_box()
            }
        }

        /// Type alias for boxed up cloneable functions that take two `Message`s and
        /// return nothing. Mainly meant to increase readability of code.
        pub type FnBox = Box<dyn FnBoxClone>;
    }

    /// Types and Traits for functions which take three arguments.
    pub mod ternary {
        use super::*;

        /// Trait to allow boxed up functions that take three `Message`s and return
        /// nothing to be cloned.
        pub trait FnBoxClone: Fn(Message, Message, Message) -> () + Send {
            fn clone_box(&self) -> Box<dyn FnBoxClone>;
        }

        impl<F> FnBoxClone for F
        where
            F: Fn(Message, Message, Message) -> () + Send + Clone + 'static,
        {
            /// Proxy function to be able to implement the `Clone` trait on
            /// boxed up functions that take three `Message`s and return nothing.
            fn clone_box(&self) -> Box<dyn FnBoxClone> {
                Box::new(self.clone())
            }
        }

        impl Clone for Box<dyn FnBoxClone> {
            fn clone(&self) -> Box<dyn FnBoxClone> {
                (**self).clone_box()
            }
        }

        /// Type alias for boxed up cloneable functions that take three `Message`s and
        /// return nothing. Mainly meant to increase readability of code.
        pub type FnBox = Box<dyn FnBoxClone>;
    }
}

/// Adds specific ID types for the various IDs that are used in the crate.
pub mod ids {
    use std::sync::atomic::{AtomicUsize, Ordering};

    /// ID to identify a channel within a Join Pattern.
    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
    pub struct ChannelId(usize);

    impl ChannelId {
        pub(crate) fn new(value: usize) -> ChannelId {
            ChannelId(value)
        }

        /// Increment the internal value of the channel ID.
        pub(crate) fn increment(&mut self) {
            self.0 += 1;
        }
    }

    /// ID to identify a Join Pattern within a Junction.
    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
    pub struct JoinPatternId(usize);

    impl JoinPatternId {
        /// Increment the internal value of the Join Pattern ID.
        pub(crate) fn increment(&mut self) {
            self.0 += 1;
        }
    }

    /// Globally synchronized counter to ensure that no two Junctions will have
    /// the same ID.
    pub static LATEST_JUNCTION_ID: AtomicUsize = AtomicUsize::new(0);

    /// ID for a Junction to identify itself.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct JunctionId(usize);

    impl JunctionId {
        pub(crate) fn new() -> JunctionId {
            JunctionId(LATEST_JUNCTION_ID.fetch_add(1, Ordering::Relaxed))
        }
    }
}