Skip to main content

fusor/
cleanup.rs

1//! Ordinary Rust guards for external work and effect executions.
2use crate::{Effect, effect, untrack};
3use std::{
4    any::Any,
5    cell::{Cell, RefCell},
6    rc::Rc,
7};
8
9/// Run a callback exactly once when dropped. Combine with an owner registration or
10/// [`effect_with_cleanup`] to release external resources.
11#[must_use = "retain the guard for the lifetime of the external work"]
12pub struct Cleanup(Option<Box<dyn FnOnce()>>);
13
14impl Cleanup {
15    pub fn new(callback: impl FnOnce() + 'static) -> Self {
16        Self(Some(Box::new(callback)))
17    }
18}
19
20impl Drop for Cleanup {
21    fn drop(&mut self) {
22        if let Some(callback) = self.0.take() {
23            untrack(callback);
24        }
25    }
26}
27
28#[derive(Default)]
29struct GuardSlot {
30    stopped: Cell<bool>,
31    value: RefCell<Option<Box<dyn Any>>>,
32}
33
34impl GuardSlot {
35    fn clear(&self) {
36        let value = self.value.take();
37        untrack(|| drop(value));
38    }
39}
40
41/// A reactive execution with a guard released before the next execution and on
42/// disposal. Destructors run without dependency tracking or registry borrows.
43#[must_use = "retain the effect while its external work should remain active"]
44pub struct CleanupEffect {
45    effect: Effect,
46    slot: Rc<GuardSlot>,
47}
48
49impl CleanupEffect {
50    pub fn dispose(&self) {
51        self.slot.stopped.set(true);
52        self.effect.dispose();
53        self.slot.clear();
54    }
55}
56
57impl Drop for CleanupEffect {
58    fn drop(&mut self) {
59        self.dispose();
60    }
61}
62
63/// Track reads during setup, retaining its returned Rust value until replacement.
64/// Return a [`Cleanup`] for callback-based APIs or any existing RAII guard.
65/// Cleanup itself is untracked. A rerun that panics has already released its old
66/// guard; Rust's ordinary unwind/abort behavior applies to the setup operation.
67pub fn effect_with_cleanup<G: 'static>(mut setup: impl FnMut() -> G + 'static) -> CleanupEffect {
68    let slot = Rc::new(GuardSlot::default());
69    let captured = slot.clone();
70    let effect = effect(move || {
71        captured.clear();
72        if captured.stopped.get() {
73            return;
74        }
75        let guard = setup();
76        if captured.stopped.get() {
77            untrack(|| drop(guard));
78        } else {
79            *captured.value.borrow_mut() = Some(Box::new(guard));
80        }
81    });
82    CleanupEffect { effect, slot }
83}