use core::{cell::UnsafeCell, future::poll_fn, task::Poll};
use rtic_common::waker_registration::CriticalSectionWakerRegistration;
#[derive(Clone, Copy)]
enum Store<T> {
Set(T),
Unset,
}
pub struct Signal<T: Copy> {
waker: CriticalSectionWakerRegistration,
store: UnsafeCell<Store<T>>,
}
impl<T> core::fmt::Debug for Signal<T>
where
T: core::marker::Copy,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt.write_fmt(format_args!(
"Signal<{}>{{ .. }}",
core::any::type_name::<T>()
))
}
}
impl<T: Copy> Default for Signal<T> {
fn default() -> Self {
Self::new()
}
}
unsafe impl<T: Copy> Send for Signal<T> {}
unsafe impl<T: Copy> Sync for Signal<T> {}
impl<T: Copy> Signal<T> {
pub const fn new() -> Self {
Self {
waker: CriticalSectionWakerRegistration::new(),
store: UnsafeCell::new(Store::Unset),
}
}
pub fn split(&self) -> (SignalWriter<T>, SignalReader<T>) {
(SignalWriter { parent: self }, SignalReader { parent: self })
}
}
#[derive(Clone)]
pub struct SignalWriter<'a, T: Copy> {
parent: &'a Signal<T>,
}
impl<T> core::fmt::Debug for SignalWriter<'_, T>
where
T: core::marker::Copy,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt.write_fmt(format_args!(
"SignalWriter<{}>{{ .. }}",
core::any::type_name::<T>()
))
}
}
impl<'a, T: Copy> SignalWriter<'a, T> {
fn write_inner(&mut self, value: Store<T>) {
critical_section::with(|_| {
unsafe { self.parent.store.get().replace(value) };
});
self.parent.waker.wake();
}
pub fn write(&mut self, value: T) {
self.write_inner(Store::Set(value));
}
pub fn clear(&mut self) {
self.write_inner(Store::Unset);
}
}
pub struct SignalReader<'a, T: Copy> {
parent: &'a Signal<T>,
}
impl<T> core::fmt::Debug for SignalReader<'_, T>
where
T: core::marker::Copy,
{
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt.write_fmt(format_args!(
"SignalReader<{}>{{ .. }}",
core::any::type_name::<T>()
))
}
}
impl<'a, T: Copy> SignalReader<'a, T> {
fn take(&mut self) -> Store<T> {
critical_section::with(|_| {
unsafe { self.parent.store.get().replace(Store::Unset) }
})
}
pub fn try_read(&mut self) -> Option<T> {
match self.take() {
Store::Unset => None,
Store::Set(value) => Some(value),
}
}
pub async fn wait(&mut self) -> T {
poll_fn(|ctx| {
self.parent.waker.register(ctx.waker());
match self.take() {
Store::Unset => Poll::Pending,
Store::Set(value) => Poll::Ready(value),
}
})
.await
}
pub async fn wait_fresh(&mut self) -> T {
self.take();
self.wait().await
}
}
#[macro_export]
macro_rules! make_signal {
( $T:ty ) => {{
static SIGNAL: Signal<$T> = Signal::new();
SIGNAL.split()
}};
}
#[cfg(test)]
mod tests {
use static_cell::StaticCell;
use super::*;
#[test]
fn empty() {
let (_writer, mut reader) = make_signal!(u32);
assert!(reader.try_read().is_none());
}
#[test]
fn ping_pong() {
let (mut writer, mut reader) = make_signal!(u32);
writer.write(0xde);
assert!(reader.try_read().is_some_and(|value| value == 0xde));
}
#[test]
fn latest() {
let (mut writer, mut reader) = make_signal!(u32);
writer.write(0xde);
writer.write(0xad);
writer.write(0xbe);
writer.write(0xef);
assert!(reader.try_read().is_some_and(|value| value == 0xef));
}
#[test]
fn consumption() {
let (mut writer, mut reader) = make_signal!(u32);
writer.write(0xaa);
assert!(reader.try_read().is_some_and(|value| value == 0xaa));
assert!(reader.try_read().is_none());
}
#[tokio::test]
async fn pending() {
let (mut writer, mut reader) = make_signal!(u32);
writer.write(0xaa);
assert_eq!(reader.wait().await, 0xaa);
}
#[tokio::test]
async fn waiting() {
static READER: StaticCell<SignalReader<u32>> = StaticCell::new();
let (mut writer, reader) = make_signal!(u32);
writer.write(0xaa);
let reader = READER.init(reader);
let handle = tokio::spawn(reader.wait_fresh());
tokio::task::yield_now().await; assert!(!handle.is_finished()); writer.write(0xab);
assert!(handle.await.is_ok_and(|value| value == 0xab));
}
}