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::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}