sauron_core/dom/program/
app_context.rs

1#[cfg(feature = "with-measure")]
2use crate::dom::Measurements;
3use crate::dom::{Application, Dispatch};
4use crate::vdom;
5use std::{
6    cell::{Ref, RefCell},
7    collections::VecDeque,
8    rc::Rc,
9    rc::Weak,
10};
11
12/// AppContext module pertains only to application state and manages objects that affects it.
13/// It has no access to the dom, threads or any of the processing details that Program has to do.
14pub(crate) struct AppContext<APP>
15where
16    APP: Application,
17{
18    /// holds the user application
19    pub(crate) app: Rc<RefCell<APP>>,
20
21    /// the current vdom representation
22    /// if the dom is sync with the app state the current_vdom corresponds to the app.view
23    pub(crate) current_vdom: Rc<RefCell<vdom::Node<APP::MSG>>>,
24
25    /// The MSG that hasn't been applied to the APP yet
26    ///
27    /// Note: MSG has to be executed in the same succession one by one
28    /// since the APP's state may be affected by the previous MSG
29    pub(crate) pending_msgs: Rc<RefCell<VecDeque<APP::MSG>>>,
30
31    /// pending cmds that hasn't been emited yet
32    pub(crate) pending_dispatches: Rc<RefCell<VecDeque<Dispatch<APP>>>>,
33}
34
35pub(crate) struct WeakContext<APP>
36where
37    APP: Application,
38{
39    pub(crate) app: Weak<RefCell<APP>>,
40    pub(crate) current_vdom: Weak<RefCell<vdom::Node<APP::MSG>>>,
41    pub(crate) pending_msgs: Weak<RefCell<VecDeque<APP::MSG>>>,
42    pub(crate) pending_dispatches: Weak<RefCell<VecDeque<Dispatch<APP>>>>,
43}
44
45impl<APP> WeakContext<APP>
46where
47    APP: Application,
48{
49    pub(crate) fn upgrade(&self) -> Option<AppContext<APP>> {
50        let app = self.app.upgrade()?;
51        let current_vdom = self.current_vdom.upgrade()?;
52        let pending_msgs = self.pending_msgs.upgrade()?;
53        let pending_dispatches = self.pending_dispatches.upgrade()?;
54        Some(AppContext {
55            app,
56            current_vdom,
57            pending_msgs,
58            pending_dispatches,
59        })
60    }
61}
62
63impl<APP> Clone for WeakContext<APP>
64where
65    APP: Application,
66{
67    fn clone(&self) -> Self {
68        Self {
69            app: Weak::clone(&self.app),
70            current_vdom: Weak::clone(&self.current_vdom),
71            pending_msgs: Weak::clone(&self.pending_msgs),
72            pending_dispatches: Weak::clone(&self.pending_dispatches),
73        }
74    }
75}
76
77impl<APP> AppContext<APP>
78where
79    APP: Application,
80{
81    pub(crate) fn downgrade(this: &Self) -> WeakContext<APP> {
82        WeakContext {
83            app: Rc::downgrade(&this.app),
84            current_vdom: Rc::downgrade(&this.current_vdom),
85            pending_msgs: Rc::downgrade(&this.pending_msgs),
86            pending_dispatches: Rc::downgrade(&this.pending_dispatches),
87        }
88    }
89    pub fn strong_count(&self) -> usize {
90        Rc::strong_count(&self.app)
91    }
92    pub fn weak_count(&self) -> usize {
93        Rc::weak_count(&self.app)
94    }
95}
96
97impl<APP> Clone for AppContext<APP>
98where
99    APP: Application,
100{
101    fn clone(&self) -> Self {
102        Self {
103            app: Rc::clone(&self.app),
104            current_vdom: Rc::clone(&self.current_vdom),
105            pending_msgs: Rc::clone(&self.pending_msgs),
106            pending_dispatches: Rc::clone(&self.pending_dispatches),
107        }
108    }
109}
110
111impl<APP> AppContext<APP>
112where
113    APP: Application,
114{
115    pub fn init_app(&self) -> Dispatch<APP> {
116        Dispatch::from(self.app.borrow_mut().init())
117    }
118
119    pub fn view(&self) -> vdom::Node<APP::MSG> {
120        self.app.borrow().view()
121    }
122    pub fn dynamic_style(&self) -> String {
123        self.app.borrow().style().join("")
124    }
125
126    pub fn static_style(&self) -> String {
127        APP::stylesheet().join("")
128    }
129
130    pub fn set_current_dom(&mut self, new_vdom: vdom::Node<APP::MSG>) {
131        *self.current_vdom.borrow_mut() = new_vdom;
132    }
133
134    pub fn current_vdom(&self) -> Ref<'_, vdom::Node<APP::MSG>> {
135        self.current_vdom.borrow()
136    }
137
138    #[cfg(feature = "with-measure")]
139    pub fn measurements(&mut self, measurements: Measurements) {
140        self.app.borrow_mut().measurements(measurements)
141    }
142
143    pub fn push_msgs(&mut self, msgs: impl IntoIterator<Item = APP::MSG>) {
144        self.pending_msgs.borrow_mut().extend(msgs);
145    }
146
147    pub fn update_app(&mut self, msg: APP::MSG) -> Dispatch<APP> {
148        Dispatch::from(self.app.borrow_mut().update(msg))
149    }
150
151    /// return true if there are still pending msgs
152    pub fn has_pending_msgs(&self) -> bool {
153        !self.pending_msgs.borrow().is_empty()
154    }
155
156    /// return the current number of pending msgs
157    #[cfg(feature = "ensure-check")]
158    pub fn pending_msgs_count(&self) -> usize {
159        self.pending_msgs.borrow().len()
160    }
161
162    /// dispatch a single pending msg, return true successfully dispatch one
163    /// false if there is no more pending msg
164    pub fn dispatch_pending_msg(&mut self) -> bool {
165        let pending_msg = self.pending_msgs.borrow_mut().pop_front();
166        let cmd = if let Some(pending_msg) = pending_msg {
167            // Note: each MSG needs to be executed one by one in the same order
168            // as APP's state can be affected by the previous MSG
169            let cmd = self.update_app(pending_msg);
170            Some(cmd)
171        } else {
172            None
173        };
174
175        if let Some(cmd) = cmd {
176            // we put the cmd in the pending_cmd queue
177            self.pending_dispatches.borrow_mut().push_back(cmd);
178            true
179        } else {
180            false
181        }
182    }
183
184    pub fn batch_pending_cmds(&mut self) -> Dispatch<APP> {
185        Dispatch::batch(self.pending_dispatches.borrow_mut().drain(..))
186    }
187}