rt_graph/
signal.rs

1/// Implements a broadcast-listener / callback / observable pattern.
2///
3/// `Signal` holds a list of subscriptions, each with a callback closure to run
4/// on the next broadcast.
5///
6/// As `rt-graph` uses GTK, the terminology (`Signal` struct and its method names) match
7/// GTK's terms.
8pub struct Signal<T: Clone> {
9    subs: Vec<Subscription<T>>,
10    new_id: usize,
11}
12
13struct Subscription<T> {
14    id: SubscriptionId,
15    callback: Box<dyn Fn(T)>,
16}
17
18/// The identifier for a subscription, used to disconnect it when no longer required.
19#[derive(Clone, Copy, Eq, PartialEq)]
20pub struct SubscriptionId(usize);
21
22impl<T: Clone> Signal<T> {
23    /// Construct a new `Signal`.
24    pub fn new() -> Signal<T> {
25        Signal {
26            subs: Vec::with_capacity(0),
27            new_id: 0,
28        }
29    }
30
31    /// Connect a new subscriber that will receive callbacks when the
32    /// signal is raised.
33    ///
34    /// Returns a SubscriptionId to disconnect the subscription when
35    /// no longer required.
36    pub fn connect<F>(&mut self, callback: F) -> SubscriptionId
37        where F: (Fn(T)) + 'static
38    {
39        let id = SubscriptionId(self.new_id);
40        self.new_id = self.new_id.checked_add(1).expect("No overflow");
41
42        self.subs.push(Subscription {
43            id,
44            callback: Box::new(callback),
45        });
46        self.subs.shrink_to_fit();
47
48        id
49    }
50
51    /// Notify existing subscribers.
52    pub fn raise(&self, value: T) {
53        for sub in self.subs.iter() {
54            (sub.callback)(value.clone())
55        }
56    }
57
58    /// Disconnect an existing subscription.
59    pub fn disconnect(&mut self, id: SubscriptionId) {
60        self.subs.retain(|sub| sub.id != id);
61        self.subs.shrink_to_fit();
62    }
63}
64
65#[cfg(test)]
66mod test {
67    use crate::Signal;
68    use std::{cell::Cell, rc::Rc};
69
70    #[test]
71    fn signal() {
72        let mut sig = Signal::new();
73
74        let data: Rc<Cell<u32>> = Rc::new(Cell::new(0));
75        assert_eq!(data.get(), 0);
76
77        let dc = data.clone();
78        let subid = sig.connect(move |v| {
79            dc.set(dc.get() + v);
80        });
81        assert_eq!(data.get(), 0);
82
83        sig.raise(1);
84        assert_eq!(data.get(), 1);
85
86        sig.raise(2);
87        assert_eq!(data.get(), 3);
88
89        sig.disconnect(subid);
90
91        sig.raise(0);
92        assert_eq!(data.get(), 3);
93    }
94
95    #[test]
96    fn signal_multiple_subscriptions() {
97        let mut sig = Signal::new();
98
99        let data: Rc<Cell<u32>> = Rc::new(Cell::new(0));
100        assert_eq!(data.get(), 0);
101
102        let dc = data.clone();
103        let sub1 = sig.connect(move |_v| {
104            dc.set(dc.get() + 1);
105        });
106        let dc = data.clone();
107        let sub2 = sig.connect(move |_v| {
108            dc.set(dc.get() + 10);
109        });
110
111        sig.raise(0);
112        assert_eq!(data.get(), 11);
113
114        sig.disconnect(sub1);
115
116        sig.raise(0);
117        assert_eq!(data.get(), 21);
118
119        sig.disconnect(sub2);
120        sig.raise(0);
121
122        sig.raise(0);
123        assert_eq!(data.get(), 21);
124    }
125}