Skip to main content

lactea/
callbacks.rs

1//! Host callbacks (`callbacks.h`): `projectm_set_preset_switch_requested_event_callback` and
2//! `projectm_set_preset_switch_failed_event_callback`.
3//!
4//! A [`Hook`] is an optional, cheaply clonable `Fn` that the owner invokes synchronously from the
5//! call that raises the event (the same threading contract as projectM: the callback runs on the
6//! caller's thread, inside the call).
7
8use std::fmt;
9use std::sync::Arc;
10
11/// An optional callback taking `&A`. Clones share the same underlying callback.
12pub struct Hook<A>(Option<Callback<A>>);
13
14type Callback<A> = Arc<dyn Fn(&A) + Send + Sync>;
15
16impl<A> Hook<A> {
17    /// A hook with no callback installed.
18    pub const fn none() -> Self {
19        Self(None)
20    }
21
22    /// Install `f`, or remove the callback with `None`.
23    pub fn set(&mut self, f: Option<impl Fn(&A) + Send + Sync + 'static>) {
24        self.0 = f.map(|f| Arc::new(f) as Callback<A>);
25    }
26
27    /// Whether a callback is installed.
28    pub fn is_set(&self) -> bool {
29        self.0.is_some()
30    }
31
32    /// Call the callback, if any.
33    pub fn fire(&self, arg: &A) {
34        if let Some(f) = &self.0 {
35            f(arg);
36        }
37    }
38}
39
40impl<A> Default for Hook<A> {
41    fn default() -> Self {
42        Self::none()
43    }
44}
45
46impl<A> Clone for Hook<A> {
47    fn clone(&self) -> Self {
48        Self(self.0.clone())
49    }
50}
51
52impl<A> fmt::Debug for Hook<A> {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        f.write_str(if self.0.is_some() {
55            "Hook(set)"
56        } else {
57            "Hook(none)"
58        })
59    }
60}
61
62/// A failed preset load (`projectm_preset_switch_failed_event`): the preset's name and the
63/// error text. The previously loaded preset keeps running.
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct SwitchFailure {
66    /// Name of the preset that failed to load.
67    pub name: String,
68    /// Error text of the failed load.
69    pub message: String,
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use std::sync::atomic::{AtomicU32, Ordering};
76
77    #[test]
78    fn hook_fires_until_removed() {
79        let n = Arc::new(AtomicU32::new(0));
80        let mut h = Hook::<u32>::none();
81        h.fire(&1);
82        let n2 = n.clone();
83        h.set(Some(move |v: &u32| {
84            n2.fetch_add(*v, Ordering::SeqCst);
85        }));
86        let copy = h.clone();
87        h.fire(&2);
88        copy.fire(&3);
89        assert_eq!(n.load(Ordering::SeqCst), 5);
90        h.set(None::<fn(&u32)>);
91        h.fire(&10);
92        assert_eq!(n.load(Ordering::SeqCst), 5);
93    }
94}