1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
// MIT/Apache2 License

//! `breadthread` is an abstraction for what I've noticed is a very common pattern for low-level graphics APIs
//! to have:
//!
//! * One thread is designated the "GUI Thread". All GUI operations must occur in this thread.
//! * Primitives are thread-unsafe and can only be used safely in the GUI thread.
//! * In order to run the framework, an event loop function has to be called in a loop.
//! * This "main loop" produces events.
//!
//! In order to abstract over this model in a not just memory-safe, but thread-safe way, the `breadthread`
//! package:
//!
//! * Provides the [`BreadThread`] object. When this object is instantiated, the thread it's instantiated in
//!   becomes the "bread thread". The `BreadThread` is `!Send` and thus remains fixed to the bread thread.
//! * The `BreadThread` is provided an object implementing the [`Controller`] trait, which determines how it
//!   runs the main event loop.
//! * Objects implementing the [`Directive`] trait are used to send "messages" to the `Controller`, telling it
//!   what operations it should run.
//! * The `BreadThread` also creates "events". An "event handler" can be provided to the `BreadThread` in order
//!   to run something whenever an event is generated.
//! * While the `BreadThread` is `!Send`, it can create [`ThreadHandle`]s that can be sent to other threads.
//! * When the first `ThreadHandle` is created, the "directive thread" is spawned. This thread listens for events
//!   and then pushes them into the controller's directive queue.
//! * When the `ThreadHandle` is used from another thread, it sends its directives to the directive thread.
//!   However, if it is used from the bread thread, it forwards its directives straight to the controller.
//!
//! # Features
//!
//! `breadthread` uses mutexes internally. When the `pl` feature is enabled, it uses `parking_lot`
//! mutexes instead of `std::sync` mutexes. This may be useful in programs that already use `parking_lot`.

#![forbid(unsafe_code)]

mod bread_thread;
mod completer;
mod controller;
mod directive;
mod key;
mod thread_handle;
mod thread_state;

pub(crate) mod directive_thread;
pub(crate) mod mutex;

pub use bread_thread::*;
pub use completer::*;
pub use controller::*;
pub use directive::*;
pub use key::*;
pub use thread_handle::*;

pub(crate) use thread_state::*;

use std::{error::Error as StdError, fmt, num::NonZeroUsize};
use thread_safe::NotInOriginThread;

/// An error occurred during operation of `breadthread`.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Error<InnerError> {
    /// Directive contained external pointers that didn't belong.
    InvalidPtr(NonZeroUsize),
    /// The bread thread has ceased operation.
    Closed,
    /// The controller failed to complete processing a directive.
    UnableToComplete,
    /// Attempted to turn a thread that was already a bread thread into a bread thread.
    AlreadyABreadThread,
    /// We are not in the bread thread.
    NotInBreadThread,
    /// A controller-associated error has occurred.
    Controller(InnerError),
}

impl<InnerError> From<NotInOriginThread> for Error<InnerError> {
    #[inline]
    fn from(_: NotInOriginThread) -> Error<InnerError> {
        Error::NotInBreadThread
    }
}

impl<InnerError: fmt::Display> fmt::Display for Error<InnerError> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::InvalidPtr(ptr) => {
                write!(f, "Directive contained external pointer that does not belong to the bread thread: {:#08X}", ptr)
            }
            Error::Closed => f.write_str("The bread thread is closed and cannot be used"),
            Error::UnableToComplete => f.write_str("The controller did not complete the directive"),
            Error::AlreadyABreadThread => f.write_str("This thread is already a bread thread"),
            Error::NotInBreadThread => f.write_str("This thread is not the bread thread"),
            Error::Controller(inner) => fmt::Display::fmt(inner, f),
        }
    }
}

impl<InnerError: fmt::Display + fmt::Debug> StdError for Error<InnerError> {
    #[inline]
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        match self {
            Error::NotInBreadThread => Some(&NotInOriginThread),
            _ => None,
        }
    }
}