tokens/
composite.rs

1use crate::{Callback, ChangeToken, Registration, SharedChangeToken, SingleChangeToken};
2use std::{
3    any::Any,
4    sync::{Arc, Weak},
5};
6
7struct Mediator {
8    parent: SharedChangeToken<SingleChangeToken>,
9    children: Vec<Box<dyn ChangeToken>>,
10    _registrations: Vec<Registration>,
11}
12
13impl Mediator {
14    fn new(
15        parent: SharedChangeToken<SingleChangeToken>,
16        tokens: Vec<Box<dyn ChangeToken>>,
17    ) -> Arc<Self> {
18        Arc::new_cyclic(|me| {
19            let registrations = Self::register(me, &tokens);
20            Self {
21                parent,
22                children: tokens,
23                _registrations: registrations,
24            }
25        })
26    }
27
28    fn register(me: &Weak<Self>, tokens: &[Box<dyn ChangeToken>]) -> Vec<Registration> {
29        let mut registrations = Vec::with_capacity(tokens.len());
30
31        for token in tokens {
32            if token.must_poll() {
33                continue;
34            }
35
36            let registration = token.register(
37                Box::new(|state| {
38                    let weak = state.unwrap();
39                    if let Some(this) = weak.downcast_ref::<Weak<Self>>().unwrap().upgrade() {
40                        this.parent.notify();
41                    }
42                }),
43                Some(Arc::new(me.clone())),
44            );
45
46            registrations.push(registration);
47        }
48
49        registrations
50    }
51}
52
53/// Represents a composition of one or more [`ChangeToken`](crate::ChangeToken) instances.
54pub struct CompositeChangeToken {
55    inner: SharedChangeToken<SingleChangeToken>,
56    mediator: Arc<Mediator>,
57}
58
59impl CompositeChangeToken {
60    /// Initializes a new composite change token.
61    ///
62    /// # Arguments
63    ///
64    /// * `tokens` - A sequence of [`ChangeToken`](crate::ChangeToken) instances
65    pub fn new(tokens: impl Iterator<Item = Box<dyn ChangeToken>>) -> Self {
66        let inner = SharedChangeToken::<SingleChangeToken>::default();
67        let shared: SharedChangeToken<SingleChangeToken> = inner.clone();
68        Self {
69            inner,
70            mediator: Mediator::new(shared, tokens.collect()),
71        }
72    }
73
74    /// Notifies any registered callbacks of a change.
75    pub fn notify(&self) {
76        self.inner.notify()
77    }
78}
79
80impl ChangeToken for CompositeChangeToken {
81    fn changed(&self) -> bool {
82        self.inner.changed() || self.mediator.children.iter().any(|t| t.changed())
83    }
84
85    fn must_poll(&self) -> bool {
86        self.mediator.children.iter().all(|t| t.must_poll())
87    }
88
89    fn register(&self, callback: Callback, state: Option<Arc<dyn Any>>) -> Registration {
90        self.inner.register(callback, state)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96
97    use super::*;
98    use crate::*;
99    use std::iter::empty;
100    use std::sync::{
101        atomic::{AtomicU8, Ordering},
102        Arc,
103    };
104
105    #[test]
106    fn changed_should_be_false_when_no_changes_have_occurred() {
107        // arrange
108        let token = CompositeChangeToken::new(empty());
109
110        // act
111        let changed = token.changed();
112
113        // assert
114        assert_eq!(changed, false);
115    }
116
117    #[test]
118    fn changed_should_be_true_if_child_has_changed() {
119        // arrange
120        let child = SingleChangeToken::new();
121
122        child.notify();
123
124        let child: Box<dyn ChangeToken> = Box::new(child);
125        let tokens = vec![child];
126        let token = CompositeChangeToken::new(tokens.into_iter());
127
128        // act
129        let changed = token.changed();
130
131        // assert
132        assert_eq!(changed, true);
133    }
134
135    #[test]
136    fn changed_should_be_true_if_ever_notified() {
137        // arrange
138        let child: Box<dyn ChangeToken> = Box::new(SingleChangeToken::new());
139        let tokens = vec![child];
140        let token = CompositeChangeToken::new(tokens.into_iter());
141
142        token.notify();
143
144        // act
145        let changed = token.changed();
146
147        // assert
148        assert_eq!(changed, true);
149    }
150
151    #[test]
152    fn must_poll_should_be_true_all_tokens_do_not_support_callbacks() {
153        // arrange
154        let child: Box<dyn ChangeToken> = Box::new(NeverChangeToken::new());
155        let tokens = vec![child];
156        let token = CompositeChangeToken::new(tokens.into_iter());
157
158        // act
159        let poll_required = token.must_poll();
160
161        // assert
162        assert_eq!(poll_required, true);
163    }
164
165    #[test]
166    fn must_poll_should_be_false_if_at_least_one_token_supports_callbacks() {
167        // arrange
168        let tokens: Vec<Box<dyn ChangeToken>> = vec![
169            Box::new(NeverChangeToken::new()),
170            Box::new(SingleChangeToken::new()),
171        ];
172        let token = CompositeChangeToken::new(tokens.into_iter());
173
174        // act
175        let poll_required = token.must_poll();
176
177        // assert
178        assert_eq!(poll_required, false);
179    }
180
181    #[test]
182    fn child_should_trigger_parent_callbacks() {
183        // arrange
184        let child = SharedChangeToken::<DefaultChangeToken>::default();
185        let tokens: Vec<Box<dyn ChangeToken>> = vec![Box::new(child.clone())];
186        let token = CompositeChangeToken::new(tokens.into_iter());
187        let counter = Arc::new(AtomicU8::default());
188        let _registration = token.register(
189            Box::new(|state| {
190                state
191                    .unwrap()
192                    .downcast_ref::<AtomicU8>()
193                    .unwrap()
194                    .fetch_add(1, Ordering::SeqCst);
195            }),
196            Some(counter.clone()),
197        );
198
199        // act
200        child.notify();
201
202        // assert
203        assert_eq!(counter.load(Ordering::SeqCst), 1);
204    }
205
206    #[test]
207    fn child_should_not_trigger_callbacks_multiple_times() {
208        // arrange
209        let child = SharedChangeToken::<DefaultChangeToken>::default();
210        let tokens: Vec<Box<dyn ChangeToken>> = vec![Box::new(child.clone())];
211        let token = CompositeChangeToken::new(tokens.into_iter());
212        let counter = Arc::new(AtomicU8::default());
213        let _registration = token.register(
214            Box::new(|state| {
215                state
216                    .unwrap()
217                    .downcast_ref::<AtomicU8>()
218                    .unwrap()
219                    .fetch_add(1, Ordering::SeqCst);
220            }),
221            Some(counter.clone()),
222        );
223
224        child.notify();
225
226        // act
227        child.notify();
228
229        // assert
230        assert_eq!(counter.load(Ordering::SeqCst), 1);
231    }
232
233    #[test]
234    fn notify_should_not_trigger_callbacks_multiple_times() {
235        // arrange
236        let child: Box<dyn ChangeToken> = Box::new(NeverChangeToken::new());
237        let tokens = vec![child];
238        let token = CompositeChangeToken::new(tokens.into_iter());
239        let counter = Arc::new(AtomicU8::default());
240        let _registration = token.register(
241            Box::new(|state| {
242                state
243                    .unwrap()
244                    .downcast_ref::<AtomicU8>()
245                    .unwrap()
246                    .fetch_add(1, Ordering::SeqCst);
247            }),
248            Some(counter.clone()),
249        );
250
251        token.notify();
252
253        // act
254        token.notify();
255
256        // assert
257        assert_eq!(counter.load(Ordering::SeqCst), 1);
258    }
259}