Skip to main content

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::{
12    GenerationalBox,
13    Owner,
14};
15use rustc_hash::FxHashSet;
16use tiny_notify::Notify;
17
18use crate::{
19    current_context::CurrentContext,
20    runner::Message,
21    scope_id::ScopeId,
22};
23
24pub(crate) struct Inner {
25    self_rc: Option<ReactiveContext>,
26    update: Rc<dyn Fn()>,
27    subscriptions: Vec<Rc<RefCell<FxHashSet<ReactiveContext>>>>,
28}
29
30impl Drop for Inner {
31    fn drop(&mut self) {
32        let Some(self_rc) = self.self_rc.take() else {
33            return;
34        };
35        for subscription in self.subscriptions.drain(..) {
36            subscription.borrow_mut().remove(&self_rc);
37        }
38    }
39}
40
41#[derive(Clone)]
42pub struct ReactiveContext {
43    pub(crate) inner: GenerationalBox<Inner>,
44}
45
46impl Hash for ReactiveContext {
47    fn hash<H: Hasher>(&self, state: &mut H) {
48        self.inner.id().hash(state);
49    }
50}
51
52impl PartialEq for ReactiveContext {
53    fn eq(&self, other: &Self) -> bool {
54        self.inner.ptr_eq(&other.inner)
55    }
56}
57
58impl Eq for ReactiveContext {}
59
60impl ReactiveContext {
61    pub(crate) fn new_for_scope(
62        sender: UnboundedSender<Message>,
63        scope_id: ScopeId,
64        writer: &dyn Fn(Inner) -> GenerationalBox<Inner>,
65    ) -> Self {
66        let rc = Self {
67            inner: (writer)(Inner {
68                self_rc: None,
69
70                update: Rc::new(move || {
71                    sender
72                        .unbounded_send(Message::MarkScopeAsDirty(scope_id))
73                        .unwrap();
74                }),
75                subscriptions: Vec::default(),
76            }),
77        };
78
79        rc.inner.write().self_rc = Some(rc.clone());
80
81        rc
82    }
83
84    pub fn new_for_task() -> (Notify, Self) {
85        CurrentContext::with(|context| {
86            let owner = context
87                .scopes_storages
88                .borrow()
89                .get(&context.scope_id)
90                .map(|scope| scope.owner.clone())
91                .unwrap();
92            Self::new_for_async(&owner)
93        })
94    }
95
96    /// Create an async reactive context whose storage owner must remain alive while it is used.
97    pub fn new_for_async(owner: &Owner) -> (Notify, Self) {
98        let notification = Notify::default();
99        let update_notification = notification.clone();
100        let context = Self {
101            inner: owner.insert(Inner {
102                self_rc: None,
103                update: Rc::new(move || update_notification.notify()),
104                subscriptions: Vec::default(),
105            }),
106        };
107        context.inner.write().self_rc = Some(context.clone());
108
109        (notification, context)
110    }
111
112    pub fn run<T>(new_context: Self, run: impl FnOnce() -> T) -> T {
113        new_context.clear_subscriptions();
114        REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.push(new_context));
115        let res = run();
116        REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.pop());
117
118        res
119    }
120
121    pub fn try_current() -> Option<Self> {
122        REACTIVE_CONTEXTS_STACK.with_borrow(|contexts| contexts.last().cloned())
123    }
124
125    pub fn current() -> Self {
126        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."))
127    }
128
129    pub fn notify(&self) -> bool {
130        if let Ok(inner) = self.inner.try_write() {
131            (inner.update)();
132            true
133        } else {
134            false
135        }
136    }
137
138    pub fn subscribe(&mut self, subscribers: &Rc<RefCell<FxHashSet<ReactiveContext>>>) {
139        subscribers.borrow_mut().insert(self.clone());
140        self.inner.write().subscriptions.push(subscribers.clone())
141    }
142
143    /// Unsubscribe this reactive context from all its current subscriptions.
144    pub fn clear_subscriptions(&self) {
145        for subscription in self.inner.write().subscriptions.drain(..) {
146            subscription.borrow_mut().remove(self);
147        }
148    }
149}
150
151thread_local! {
152    static REACTIVE_CONTEXTS_STACK: RefCell<Vec<ReactiveContext>> = const { RefCell::new(Vec::new()) }
153}