use crate::{
disposable::Disposable,
utils::{
id_generator::{Id, IdGenerator},
mutable::{Mutable, MutableHelper},
types::Shared,
},
};
use educe::Educe;
#[derive(Educe)]
#[educe(Debug, Default)]
enum State<D> {
#[educe(Default)]
Idle,
Building(Id),
Active(D),
Disposed,
}
#[derive(Educe)]
#[educe(Debug, Default)]
struct Inner<D> {
state: State<D>,
id_generator: IdGenerator,
}
#[derive(Educe)]
#[educe(Debug, Clone, Default)]
pub struct SharedDisposal<D>(Shared<Mutable<Inner<D>>>);
impl<D> SharedDisposal<D> {
pub fn replace(&self, disposal_builder: impl FnOnce() -> D)
where
D: Disposable,
{
let (id, superseded) = self.0.with_mut(|inner| {
if matches!(inner.state, State::Disposed) {
return (None, None);
}
let id = inner.id_generator.next_id();
match std::mem::replace(&mut inner.state, State::Building(id)) {
State::Idle | State::Building(_) => (Some(id), None),
State::Active(disposal) => (Some(id), Some(disposal)),
State::Disposed => unreachable!("the disposed state returned above"),
}
});
if let Some(disposal) = superseded {
disposal.dispose();
}
let Some(id) = id else {
return;
};
let disposable = disposal_builder();
let stale = self.0.with_mut(|inner| {
let is_current = match &inner.state {
State::Building(current) => *current == id,
State::Idle | State::Active(_) | State::Disposed => false,
};
if is_current {
inner.state = State::Active(disposable);
None
} else {
Some(disposable)
}
});
if let Some(disposable) = stale {
disposable.dispose();
}
}
}
impl<D> Disposable for SharedDisposal<D>
where
D: Disposable,
{
fn dispose(self) {
match self
.0
.with_mut(|inner| std::mem::replace(&mut inner.state, State::Disposed))
{
State::Idle | State::Building(_) | State::Disposed => {}
State::Active(disposable) => {
Disposable::dispose(disposable);
}
}
}
}