Skip to main content

ferroui_base/reactive/
composite_disposable.rs

1use super::IDisposable;
2use std::cell::{Cell, RefCell};
3use std::rc::Rc;
4
5/// A group of disposables that are disposed together.
6#[derive(Default)]
7pub struct CompositeDisposable {
8    disposables: RefCell<Vec<Rc<dyn IDisposable>>>,
9    disposed: Cell<bool>,
10}
11
12impl CompositeDisposable {
13    pub fn new() -> Self {
14        Self::default()
15    }
16
17    pub fn with_capacity(capacity: usize) -> Self {
18        Self { disposables: RefCell::new(Vec::with_capacity(capacity)), disposed: Cell::new(false) }
19    }
20
21    pub fn from_disposables(disposables: impl IntoIterator<Item = Rc<dyn IDisposable>>) -> Self {
22        Self { disposables: RefCell::new(disposables.into_iter().collect()), disposed: Cell::new(false) }
23    }
24
25    pub fn is_disposed(&self) -> bool {
26        self.disposed.get()
27    }
28
29    pub fn count(&self) -> usize {
30        self.disposables.borrow().len()
31    }
32
33    /// Adds a disposable to the group, or disposes it immediately if the group
34    /// has already been disposed.
35    pub fn add(&self, item: Rc<dyn IDisposable>) {
36        if self.disposed.get() {
37            item.dispose();
38        } else {
39            self.disposables.borrow_mut().push(item);
40        }
41    }
42
43    /// Removes and disposes the first occurrence of `item`.
44    pub fn remove(&self, item: &Rc<dyn IDisposable>) -> bool {
45        let removed = {
46            let mut list = self.disposables.borrow_mut();
47            list.iter().position(|d| Rc::ptr_eq(d, item)).map(|i| list.remove(i))
48        };
49        match removed {
50            Some(d) => {
51                d.dispose();
52                true
53            }
54            None => false,
55        }
56    }
57
58    /// Whether the group contains `item`. A disposed group is empty.
59    pub fn contains(&self, item: &Rc<dyn IDisposable>) -> bool {
60        if self.disposed.get() {
61            return false;
62        }
63
64        self.disposables.borrow().iter().any(|d| Rc::ptr_eq(d, item))
65    }
66
67    /// Copies the disposables of the group over the items of `array` that
68    /// start at `array_index`. A disposed group copies nothing.
69    ///
70    /// Panics when `array_index` is outside of the array or the array has
71    /// not enough room after it.
72    pub fn copy_to(&self, array: &mut [Rc<dyn IDisposable>], array_index: usize) {
73        if array_index >= array.len() {
74            panic!("Specified argument was out of the range of valid values. (Parameter 'arrayIndex')");
75        }
76
77        // disposed composites are always empty
78        if self.disposed.get() {
79            return;
80        }
81
82        let disposables = self.disposables.borrow();
83        if array_index + disposables.len() > array.len() {
84            // there is not enough space beyond array_index
85            // to accommodate all disposables in this composite
86            panic!("Specified argument was out of the range of valid values. (Parameter 'arrayIndex')");
87        }
88
89        for (i, d) in disposables.iter().enumerate() {
90            array[array_index + i] = d.clone();
91        }
92    }
93
94    /// Always false.
95    pub fn is_read_only(&self) -> bool {
96        false
97    }
98
99    /// Enumerates a copy of the disposables of the group. A disposed group
100    /// is empty.
101    pub fn get_enumerator(&self) -> std::vec::IntoIter<Rc<dyn IDisposable>> {
102        if self.disposed.get() {
103            return Vec::new().into_iter();
104        }
105
106        // the copy is unavoidable
107        self.disposables.borrow().clone().into_iter()
108    }
109
110    /// Disposes and removes all items without disposing the group itself.
111    pub fn clear(&self) {
112        let items = std::mem::take(&mut *self.disposables.borrow_mut());
113        for d in items {
114            d.dispose();
115        }
116    }
117}
118
119impl IDisposable for CompositeDisposable {
120    fn dispose(&self) {
121        if !self.disposed.replace(true) {
122            self.clear();
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    // Not from upstream: the original has no tests of this type.
130    use super::*;
131    use crate::reactive::Disposable;
132
133    #[test]
134    fn contains_enumerates_and_copies_its_disposables() {
135        let first = Disposable::create(|| {});
136        let second = Disposable::create(|| {});
137        let other = Disposable::create(|| {});
138        let target = CompositeDisposable::from_disposables([first.clone(), second.clone()]);
139
140        assert!(!target.is_read_only());
141        assert!(target.contains(&first));
142        assert!(!target.contains(&other));
143
144        let items: Vec<_> = target.get_enumerator().collect();
145        assert_eq!(2, items.len());
146        assert!(Rc::ptr_eq(&items[0], &first) && Rc::ptr_eq(&items[1], &second));
147
148        let mut array = vec![other.clone(), other.clone(), other.clone()];
149        target.copy_to(&mut array, 1);
150        assert!(Rc::ptr_eq(&array[0], &other));
151        assert!(Rc::ptr_eq(&array[1], &first));
152        assert!(Rc::ptr_eq(&array[2], &second));
153
154        target.dispose();
155        assert!(!target.contains(&first));
156        assert_eq!(0, target.get_enumerator().count());
157    }
158
159    #[test]
160    #[should_panic(expected = "arrayIndex")]
161    fn copy_to_throws_without_room() {
162        let item = Disposable::create(|| {});
163        let target = CompositeDisposable::from_disposables([item.clone(), item.clone()]);
164        let mut array = vec![item.clone(), item.clone()];
165        target.copy_to(&mut array, 1);
166    }
167}