bare-sync 0.1.0

no-std, no-alloc synchronization primitives
Documentation
//! A synchronization primitive for passing the latest value to a task.
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;

use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;

/// Single-slot signaling primitive for a _single_ consumer.
///
/// This is similar to a [`Channel`](embassy_sync::channel::Channel) with a buffer size of 1, except
/// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead
/// of waiting for the receiver to pop the previous value.
///
/// It is useful for sending data between tasks when the receiver only cares about
/// the latest data, and therefore it's fine to "lose" messages. This is often the case for "state"
/// updates.
///
/// For more advanced use cases, you might want to use [`Channel`](embassy_sync::channel::Channel) instead.
/// For multiple consumers, use [`Watch`](crate::watch::Watch) instead.
///
/// Signals are generally declared as `static`s and then borrowed as required.
///
/// ```
/// use embedded_sync::signal::Signal;
/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
///
/// enum SomeCommand {
///   On,
///   Off,
/// }
///
/// static SOME_SIGNAL: Signal<CriticalSectionRawMutex, SomeCommand> = Signal::new();
/// ```
pub struct Signal<M, T>
where
    M: RawMutex,
{
    inner: Mutex<M, SignalInner<T>>,
}

struct SignalInner<T> {
    value: UnsafeCell<MaybeUninit<T>>,
    signaled: Cell<bool>,
}

impl<T> SignalInner<T> {
    #[inline]
    fn write(&self, val: T) {
        unsafe { self.value.get().write(MaybeUninit::new(val)) };
        self.signaled.set(true);
    }

    #[inline]
    fn take(&self) -> T {
        self.signaled.set(false);
        unsafe { self.value.get().read().assume_init() }
    }

    #[inline]
    fn is_signaled(&self) -> bool {
        self.signaled.get()
    }
}

impl<M, T> Signal<M, T>
where
    M: RawMutex,
{
    /// Create a new `Signal`.
    pub const fn new() -> Self {
        Self {
            inner: Mutex::new(SignalInner {
                value: UnsafeCell::new(MaybeUninit::zeroed()),
                signaled: Cell::new(false),
            }),
        }
    }
}

impl<M, T> Default for Signal<M, T>
where
    M: RawMutex,
{
    fn default() -> Self {
        Self::new()
    }
}

impl<M, T> Signal<M, T>
where
    M: RawMutex,
{
    /// Mark this Signal as signaled.
    pub fn signal(&self, val: T) {
        self.inner.lock(|signal| {
            signal.write(val);
        })
    }

    /// Remove the queued value in this `Signal`, if any.
    pub fn reset(&self) {
        self.try_take();
    }

    /// non-blocking method to try and take the signal value.
    pub fn try_take(&self) -> Option<T> {
        self.inner.lock(|signal| {
            if signal.is_signaled() {
                Some(signal.take())
            } else {
                None
            }
        })
    }

    /// non-blocking method to check whether this signal has been signaled. This does not clear the signal.  
    pub fn signaled(&self) -> bool {
        self.inner.lock(|signal| signal.is_signaled())
    }
}