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 new_context.clear_subscriptions();
110 REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.push(new_context));
111 let res = run();
112 REACTIVE_CONTEXTS_STACK.with_borrow_mut(|context| context.pop());
113
114 res
115 }
116
117 pub fn try_current() -> Option<Self> {
118 REACTIVE_CONTEXTS_STACK.with_borrow(|contexts| contexts.last().cloned())
119 }
120
121 pub fn current() -> Self {
122 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."))
123 }
124
125 pub fn notify(&self) -> bool {
126 if let Ok(inner) = self.inner.try_write() {
127 (inner.update)();
128 true
129 } else {
130 false
131 }
132 }
133
134 pub fn subscribe(&mut self, subscribers: &Rc<RefCell<FxHashSet<ReactiveContext>>>) {
135 subscribers.borrow_mut().insert(self.clone());
136 self.inner.write().subscriptions.push(subscribers.clone())
137 }
138
139 pub fn clear_subscriptions(&self) {
141 for subscription in self.inner.write().subscriptions.drain(..) {
142 subscription.borrow_mut().remove(self);
143 }
144 }
145}
146
147thread_local! {
148 static REACTIVE_CONTEXTS_STACK: RefCell<Vec<ReactiveContext>> = const { RefCell::new(Vec::new()) }
149}