Skip to main content

rx_rust/disposable/
shared_disposal.rs

1use crate::{
2    disposable::Disposable,
3    utils::{
4        id_generator::{Id, IdGenerator},
5        mutable::{Mutable, MutableHelper},
6        types::Shared,
7    },
8};
9use educe::Educe;
10
11#[derive(Educe)]
12#[educe(Debug, Default)]
13enum State<D> {
14    #[educe(Default)]
15    Idle,
16    /// A disposal is being built with the lock released. The id is what the finished build
17    /// compares itself against: a later `replace` starts its own build and stores its own id
18    /// here, which is what makes the earlier one stale.
19    Building(Id),
20    Active(D),
21    Disposed,
22}
23
24#[derive(Educe)]
25#[educe(Debug, Default)]
26struct Inner<D> {
27    state: State<D>,
28    id_generator: IdGenerator,
29}
30
31#[derive(Educe)]
32#[educe(Debug, Clone, Default)]
33pub struct SharedDisposal<D>(Shared<Mutable<Inner<D>>>);
34
35impl<D> SharedDisposal<D> {
36    pub fn replace(&self, disposal_builder: impl FnOnce() -> D)
37    where
38        D: Disposable,
39    {
40        // The superseded disposal is handed back rather than disposed under the lock.
41        let (id, superseded) = self.0.with_mut(|inner| {
42            if matches!(inner.state, State::Disposed) {
43                return (None, None);
44            }
45            let id = inner.id_generator.next_id();
46            match std::mem::replace(&mut inner.state, State::Building(id)) {
47                State::Idle | State::Building(_) => (Some(id), None),
48                State::Active(disposal) => (Some(id), Some(disposal)),
49                State::Disposed => unreachable!("the disposed state returned above"),
50            }
51        });
52        if let Some(disposal) = superseded {
53            disposal.dispose();
54        }
55
56        let Some(id) = id else {
57            return;
58        };
59
60        let disposable = disposal_builder();
61
62        let stale = self.0.with_mut(|inner| {
63            let is_current = match &inner.state {
64                State::Building(current) => *current == id,
65                State::Idle | State::Active(_) | State::Disposed => false,
66            };
67            if is_current {
68                inner.state = State::Active(disposable);
69                None
70            } else {
71                // Reset, disposed, or superseded by a later build: this disposal is already dead.
72                Some(disposable)
73            }
74        });
75        if let Some(disposable) = stale {
76            disposable.dispose();
77        }
78    }
79}
80
81impl<D> Disposable for SharedDisposal<D>
82where
83    D: Disposable,
84{
85    fn dispose(self) {
86        // The state is replaced under the lock and matched after it is released, so the inner
87        // disposal is disposed outside the lock.
88        match self
89            .0
90            .with_mut(|inner| std::mem::replace(&mut inner.state, State::Disposed))
91        {
92            State::Idle | State::Building(_) | State::Disposed => {}
93            State::Active(disposable) => {
94                Disposable::dispose(disposable);
95            }
96        }
97    }
98}