use anyhow::{anyhow, Result};
use esp_idf_hal::{delay::BLOCK, task::notification};
use std::{
collections::HashSet, fmt::Debug, hash::Hash, num::NonZeroU32, sync::Arc,
};
pub trait Trigger: Debug + Eq + Hash + Sized + Send + Sync + 'static {
const ALL: &[Self];
fn as_u32(&self) -> u32;
}
#[macro_export]
macro_rules! trigger_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$($variant:ident = $value:expr),* $(,)?
}
) => {
$(#[$meta])*
#[repr(u32)]
$vis enum $name {
$($variant = $value),*
}
impl $crate::message::Trigger for $name {
const ALL: &[Self] = &[
$(Self::$variant),*
];
fn as_u32(&self) -> u32 {
match self {
$(Self::$variant => $value),*
}
}
}
};
}
fn trigger_to_nonzero<T: Trigger>(trigger: &T) -> Result<NonZeroU32> {
NonZeroU32::new(trigger.as_u32())
.ok_or_else(|| anyhow!("Invalid value for NonZeroU32"))
}
pub struct Notifier<T: Trigger> {
notifier: Arc<notification::Notifier>,
_marker: std::marker::PhantomData<T>,
}
impl<T: Trigger> Notifier<T> {
pub fn new(notifier: Arc<notification::Notifier>) -> Result<Self> {
Ok(Self {
notifier,
_marker: std::marker::PhantomData,
})
}
pub fn notify(&self, trigger: &T) -> Result<()> {
unsafe {
self.notifier.notify_and_yield(trigger_to_nonzero(trigger)?);
}
Ok(())
}
}
pub struct Dispatcher<T: Trigger> {
notification: notification::Notification,
_marker: std::marker::PhantomData<T>,
}
impl<T: Trigger> Dispatcher<T> {
pub fn new() -> Result<Self> {
Ok(Self {
notification: notification::Notification::new(),
_marker: std::marker::PhantomData,
})
}
pub fn notifier(&self) -> Result<Notifier<T>> {
Notifier::new(self.notification.notifier())
}
pub fn collect(&self) -> Result<HashSet<&'static T>> {
let mut set = HashSet::new();
let notification = self.notification.wait(BLOCK);
if let Some(notification) = notification {
let bits = notification.get();
for trigger in T::ALL {
if bits & trigger.as_u32() != 0 {
set.insert(trigger);
}
}
}
Ok(set)
}
}