freya_core/
reactive_context.rs

1use std::{
2    cell::RefCell,
3    hash::{
4        Hash,
5        Hasher,
6    },
7    rc::Rc,
8};
9
10use futures_channel::mpsc::UnboundedSender;
11use generational_box::GenerationalBox;
12use rustc_hash::FxHashSet;
13
14use crate::{
15    current_context::CurrentContext,
16    notify::Notify,
17    runner::Message,
18    scope_id::ScopeId,
19};
20
21pub(crate) struct Inner {
22    self_rc: Option<ReactiveContext>,
23    update: Rc<dyn Fn()>,
24    subscriptions: Vec<Rc<RefCell<FxHashSet<ReactiveContext>>>>,
25}
26
27impl Drop for Inner {
28    fn drop(&mut self) {
29        let Some(self_rc) = self.self_rc.take() else {
30            return;
31        };
32        for subscription in self.subscriptions.drain(..) {
33            subscription.borrow_mut().remove(&self_rc);
34        }
35    }
36}
37
38#[derive(Clone)]
39pub struct ReactiveContext {
40    pub(crate) inner: GenerationalBox<Inner>,
41}
42
43impl Hash for ReactiveContext {
44    fn hash<H: Hasher>(&self, state: &mut H) {
45        self.inner.id().hash(state);
46    }
47}
48
49impl PartialEq for ReactiveContext {
50    fn eq(&self, other: &Self) -> bool {
51        self.inner.ptr_eq(&other.inner)
52    }
53}
54
55impl Eq for ReactiveContext {}
56
57impl ReactiveContext {
58    pub(crate) fn new_for_scope(
59        sender: UnboundedSender<Message>,
60        scope_id: ScopeId,
61        writer: &dyn Fn(Inner) -> GenerationalBox<Inner>,
62    ) -> Self {
63        let rc = Self {
64            inner: (writer)(Inner {
65                self_rc: None,
66
67                update: Rc::new(move || {
68                    sender
69                        .unbounded_send(Message::MarkScopeAsDirty(scope_id))
70                        .unwrap();
71                }),
72                subscriptions: Vec::default(),
73            }),
74        };
75
76        rc.inner.write().self_rc = Some(rc.clone());
77
78        rc
79    }
80
81    pub fn new_for_task() -> (Notify, Self) {
82        let notify = Notify::default();
83
84        let rc = CurrentContext::with(|ctx| {
85            let owner = ctx
86                .scopes_storages
87                .borrow()
88                .get(&ctx.scope_id)
89                .map(|s| s.owner.clone())
90                .unwrap();
91            let notify = notify.clone();
92            Self {
93                inner: owner.insert(Inner {
94                    self_rc: None,
95                    update: Rc::new(move || {
96                        notify.notify();
97                    }),
98                    subscriptions: Vec::default(),
99                }),
100            }
101        });
102
103        rc.inner.write().self_rc = Some(rc.clone());
104
105        (notify, rc)
106    }
107
108    pub fn run<T>(new_context: Self, run: impl FnOnce() -> T) -> T {
109        for subscription in new_context.inner.write().subscriptions.drain(..) {
110            subscription.borrow_mut().remove(&new_context);
111        }
112        REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.push(new_context));
113        let res = run();
114        REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.pop());
115
116        res
117    }
118
119    pub fn try_current() -> Option<Self> {
120        REACTIVE_CONTEXTS_STACK.with_borrow(|contexts| contexts.last().cloned())
121    }
122
123    pub fn current() -> Self {
124        REACTIVE_CONTEXTS_STACK.with_borrow(|contexts| contexts.last().cloned().expect("Your trying to access a Freya reactive context outside of Freya, you might be in a separate thread or async task that is not integrated with Freya."))
125    }
126
127    pub fn notify(&self) -> bool {
128        if let Ok(inner) = self.inner.try_write() {
129            (inner.update)();
130            true
131        } else {
132            false
133        }
134    }
135
136    pub fn subscribe(&mut self, subscribers: &Rc<RefCell<FxHashSet<ReactiveContext>>>) {
137        subscribers.borrow_mut().insert(self.clone());
138        self.inner.write().subscriptions.push(subscribers.clone())
139    }
140}
141
142thread_local! {
143    static REACTIVE_CONTEXTS_STACK: RefCell<Vec<ReactiveContext>> = const { RefCell::new(Vec::new()) }
144}