1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! Reactive primitives.

mod effect;
mod signal;
mod signal_vec;

pub use effect::*;
pub use signal::*;
pub use signal_vec::*;

/// Creates a new reactive root. Generally, you won't need this method as it is called automatically in [`render`](crate::render()).
///
/// # Example
/// ```
/// use maple_core::prelude::*;
///
/// let trigger = Signal::new(());
/// let counter = Signal::new(0);
///
/// let owner = create_root(cloned!((trigger, counter) => move || {
///     create_effect(move || {
///         trigger.get(); // subscribe to trigger
///         counter.set(*counter.get_untracked() + 1);
///     });
/// }));
///
/// assert_eq!(*counter.get(), 1);
///
/// trigger.set(());
/// assert_eq!(*counter.get(), 2);
///
/// drop(owner);
/// trigger.set(());
/// assert_eq!(*counter.get(), 2); // should not be updated because owner was dropped
/// ```
#[must_use = "create_root returns the owner of the effects created inside this scope"]
pub fn create_root<'a>(callback: impl FnOnce() + 'a) -> Owner {
    /// Internal implementation: use dynamic dispatch to reduce code bloat.
    fn internal<'a>(callback: Box<dyn FnOnce() + 'a>) -> Owner {
        OWNER.with(|owner| {
            let outer_owner = owner.replace(Some(Owner::new()));
            callback();

            owner
                .replace(outer_owner)
                .expect("Owner should be valid inside the reactive root")
        })
    }

    internal(Box::new(callback))
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::rc::Rc;

    use super::*;

    #[test]
    fn drop_owner_inside_effect() {
        let owner = Rc::new(RefCell::new(None));

        *owner.borrow_mut() = Some(create_root({
            let owner = Rc::clone(&owner);
            move || {
                let owner = owner.take();
                drop(owner)
            }
        }));
    }
}