Skip to main content

ferroui_base/platform/
i_scoped_resource.rs

1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3
4/// A value that is available until it is disposed.
5pub trait IScopedResource<T> {
6    /// The value.
7    ///
8    /// Panics when the resource has been disposed.
9    fn value(&self) -> T;
10
11    /// Releases the value. Further calls do nothing.
12    fn dispose(&self);
13}
14
15/// An [`IScopedResource`] that runs an action when it is disposed.
16pub struct ScopedResource<T> {
17    disposed: Cell<bool>,
18    value: RefCell<Option<T>>,
19    dispose: RefCell<Option<Box<dyn FnOnce()>>>,
20}
21
22impl<T: Clone + 'static> ScopedResource<T> {
23    fn new(value: T, dispose: Box<dyn FnOnce()>) -> Self {
24        Self { disposed: Cell::new(false), value: RefCell::new(Some(value)), dispose: RefCell::new(Some(dispose)) }
25    }
26
27    pub fn create(value: T, dispose: impl FnOnce() + 'static) -> Rc<dyn IScopedResource<T>> {
28        Rc::new(Self::new(value, Box::new(dispose)))
29    }
30}
31
32impl<T: Clone + 'static> IScopedResource<T> for ScopedResource<T> {
33    fn dispose(&self) {
34        if !self.disposed.replace(true) {
35            let disp = self.dispose.borrow_mut().take();
36            let value = self.value.borrow_mut().take();
37            drop(value);
38            if let Some(disp) = disp {
39                disp();
40            }
41        }
42    }
43
44    fn value(&self) -> T {
45        if self.disposed.get() {
46            panic!("Cannot access a disposed object: ScopedResource");
47        }
48        match self.value.borrow().as_ref() {
49            Some(value) => value.clone(),
50            None => panic!("Cannot access a disposed object: ScopedResource"),
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    // Not from upstream: the original has no tests of this type.
58    use super::*;
59
60    #[test]
61    fn dispose_runs_the_action_once() {
62        let count = Rc::new(Cell::new(0));
63        let c = count.clone();
64        let resource = ScopedResource::create(5, move || c.set(c.get() + 1));
65
66        assert_eq!(5, resource.value());
67        assert_eq!(0, count.get());
68
69        resource.dispose();
70        resource.dispose();
71        assert_eq!(1, count.get());
72    }
73
74    #[test]
75    #[should_panic(expected = "Cannot access a disposed object")]
76    fn value_of_a_disposed_resource_throws() {
77        let resource = ScopedResource::create(5, || {});
78        resource.dispose();
79        resource.value();
80    }
81}