freya_core/
reactive_context.rs1use 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 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 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}