dioxus_shareables/shared.rs
1//! Module `shared` - Shared values.
2//!
3//! The easiest way to add a shared value to your dioxus app is to use the
4//! [`sharable!`](crate::shareable) macro:
5//!
6//! NOTE: The type of the shared data must be `Send + Sync`.
7//!
8//! ```rust
9//! # use dioxus::prelude::*;
10//! use dioxus_shareables::shareable;
11//!
12//! shareable!(Var: usize = 900);
13//!
14//! #[allow(non_snake_case)]
15//! pub fn Reader(cx: Scope) -> Element {
16//! let r = *Var.use_rw(&cx).read(); // this component will update when Var changes.
17//! cx.render(rsx! {
18//! "The number is: {r}"
19//! })
20//! }
21//!
22//! #[allow(non_snake_case)]
23//! pub fn Writer(cx: Scope) -> Element {
24//! let w1 = Var.use_w(&cx); // this component writes to Var, but does not get updated when Var
25//! // changes
26//! let w2 = w1.clone();
27//! cx.render(rsx! {
28//! button {
29//! onclick: move |_| { *w1.write() += 1; },
30//! "+"
31//! }
32//! button {
33//! onclick: move |_| { *w2.write() -= 1; },
34//! "-"
35//! }
36//! })
37//! }
38//! ```
39
40use parking_lot::{
41 MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
42};
43use rustc_hash::FxHashMap;
44use std::sync::Arc;
45
46type LinkUpdateMap = FxHashMap<usize, (usize, Arc<dyn Send + Sync + Fn()>)>;
47/// The actual shared data.
48pub(crate) struct Link<T>(RwLock<(T, LinkUpdateMap)>);
49impl<T> Link<T> {
50 pub(crate) fn new(t: T) -> Self {
51 Self(RwLock::new((t, FxHashMap::default())))
52 }
53 pub(crate) fn add_listener<F: FnOnce() -> Arc<dyn Send + Sync + Fn()>>(&self, id: usize, f: F) {
54 self.0.write().1.entry(id).or_insert_with(|| (0, f())).0 += 1;
55 }
56 pub(crate) fn drop_listener(&self, id: usize) {
57 let mut p = self.0.write();
58 let c = if let Some((c, _)) = p.1.get_mut(&id) {
59 *c -= 1;
60 *c
61 } else {
62 1
63 };
64 if c == 0 {
65 p.1.remove(&id);
66 }
67 }
68 pub(crate) fn needs_update(&self) {
69 for (_id, (_, u)) in self.0.read().1.iter().filter(|&(_, &(ct, _))| ct > 0) {
70 u()
71 }
72 }
73 pub(crate) fn borrow(&self) -> MappedRwLockReadGuard<T> {
74 RwLockReadGuard::map(self.0.read(), |(r, _)| r)
75 }
76 pub(crate) fn borrow_mut(&self) -> MappedRwLockWriteGuard<T> {
77 RwLockWriteGuard::map(self.0.write(), |(r, _)| r)
78 }
79}
80#[cfg(feature = "debug")]
81impl<T: std::fmt::Debug> std::fmt::Debug for Link<T> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 if let Ok(me) = (self.0).try_borrow() {
84 write!(f, "Link({:?})", me.0)
85 } else {
86 f.write_str("Link::AlreadyBorrowed")
87 }
88 }
89}
90
91/// The storage type for a shared global.
92///
93/// This is generally not used directly, but it is the type of a static declared with the
94/// [`shareable!`](`crate::shareable`) macro, and can be used to construct more complicated shared
95/// types.
96pub struct Shareable<T>(pub(crate) Option<Arc<Link<T>>>);
97impl<T> Shareable<T> {
98 pub const fn new() -> Self {
99 Self(None)
100 }
101}
102#[cfg(feature = "debug")]
103impl<T: std::fmt::Debug> std::fmt::Debug for Shareable<T> {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 if let Some(me) = &self.0 {
106 write!(f, "Shareable::Initialized({:?})", me)
107 } else {
108 write!(f, "Shareable::Uninitialized")
109 }
110 }
111}
112
113/// Declare a global variable for use as [`Shared`] hook.
114///
115/// _Example:_
116/// ```
117/// # use dioxus::prelude::*;
118/// dioxus_shareables::shareable!(#[doc(hidden)] Var: usize = 900); // Declares a type Var which can be used to
119/// // access the global.
120///
121/// fn component(cx: Scope) -> Element {
122/// let rw_hook = Var.use_rw(&cx);
123/// let w_hook = Var.use_w(&cx);
124/// // ...
125/// # cx.render(rsx! {div {}})
126/// }
127/// ```
128#[macro_export]
129macro_rules! shareable {
130 ($(#[$meta:meta])*$vis:vis $IDENT:ident: $Ty:ty = $($init:tt)*) => {
131 $(#[$meta])*
132 #[derive(Clone, Copy)]
133 $vis struct $IDENT;
134 impl $IDENT {
135 /// Obtain a RW pointer to the shared value.
136 ///
137 /// `cx` will be marked as needing update each time you call `.write()` or
138 /// `.set()` on this value.
139 pub fn use_rw<'a, P>(self,cx: &$crate::reexported::Scope<'a, P>) -> &'a mut $crate::Shared<$Ty, $crate::RW> {
140 $crate::shared::Static::_use_rw(self, cx)
141 }
142 /// Obtain a write pointer to the shared value.
143 ///
144 /// Note, this doesn't prevent you from reading the data, but raher indicates the
145 /// relationship between your component and the data.
146 ///
147 /// The promise you are making when you `use_w` is that your component does not
148 /// need to know when the value changes; i.e., you might read the value, but it
149 /// doesn't change what you display.
150 pub fn use_w<'a, P>(self,cx: &$crate::reexported::Scope<'a, P>) -> &'a mut $crate::Shared<$Ty, $crate::W> {
151 $crate::shared::Static::_use_w(self, cx)
152 }
153 /// Get a pointer to the value, but don't call 'use_hook'.
154 ///
155 /// This is generally to be avoided in components, but should be used when the shared
156 /// value must be initialized within a loop, or within the initializer of another hook.
157 ///
158 /// If you don't know why you should be using it, use either [`use_rw`](Self::use_rw)
159 /// or [`use_w`](Self::use_w) instead.
160 pub fn share(self) -> $crate::Shared<$Ty, $crate::W> {
161 $crate::shared::Static::_share(self)
162 }
163 }
164 const _: () = {
165 // We declare the static as mutable because we are not thread-safe yet.
166 #[allow(non_upper_case_globals)]
167 static $IDENT: $crate::reexported::Mutex<$crate::shared::Shareable<$Ty>> = $crate::reexported::Mutex::new($crate::shared::Shareable::new());
168 #[doc(hidden)]
169 impl $crate::shared::Static for $IDENT {
170 type Type = $Ty;
171 fn _share(self) -> $crate::Shared<$Ty, $crate::W> {
172 $crate::Shared::from_shareable(&mut $IDENT.lock(), || {$($init)*})
173 }
174 fn _use_rw<'a, P>(self,cx: &$crate::reexported::Scope<'a, P>) -> &'a mut $crate::Shared<$Ty, $crate::RW> {
175 $crate::Shared::init(cx, &mut $IDENT.lock(), || {$($init)*}, $crate::RW)
176 }
177 fn _use_w<'a, P>(self,cx: &$crate::reexported::Scope<'a, P>) -> &'a mut $crate::Shared<$Ty, $crate::W> {
178 $crate::Shared::init(cx, &mut $IDENT.lock(), || {$($init)*}, $crate::W)
179 }
180 }
181 };
182 };
183}
184
185#[doc(hidden)]
186pub trait Static {
187 type Type;
188 fn _share(self) -> Shared<Self::Type, super::W>;
189 fn _use_rw<'a, P>(
190 self,
191 cx: &dioxus_core::Scope<'a, P>,
192 ) -> &'a mut Shared<Self::Type, super::RW>;
193 fn _use_w<'a, P>(self, cx: &dioxus_core::Scope<'a, P>) -> &'a mut Shared<Self::Type, super::W>;
194}
195
196/// A hook to a shared_value.
197///
198/// This is generally created by calling `use_rw` or `use_w` on a [`shareable!`], or by
199/// calling [`ListEntry::use_rw`](`crate::list::ListEntry::use_rw`) or
200/// [`ListEntry::use_w`](`crate::list::ListEntry::use_w`).
201pub struct Shared<T: 'static, B: 'static> {
202 pub(crate) link: Arc<Link<T>>,
203 pub id: Option<usize>,
204 __: std::marker::PhantomData<B>,
205}
206impl<T: 'static, B: 'static> Clone for Shared<T, B> {
207 fn clone(&self) -> Self {
208 if let Some(id) = self.id {
209 self.link.add_listener(id, || Arc::new(|| {}))
210 }
211 Self {
212 link: self.link.clone(),
213 id: self.id,
214 __: std::marker::PhantomData,
215 }
216 }
217}
218
219impl<T: 'static, B: 'static + super::Flag> Shared<T, B> {
220 /// Initialize the hook in scope `cx`.
221 ///
222 /// The shared value will be initialized with `f()` if it hasn't been created yet.
223 ///
224 /// NOTE: this method should generally not be used directly; instead, shared values are usually
225 /// created with [`shareable!`].
226 pub fn init<'a, P, F: FnOnce() -> T>(
227 cx: &dioxus_core::Scope<'a, P>,
228 opt: &mut Shareable<T>,
229 f: F,
230 _: B,
231 ) -> &'a mut Self {
232 let id = cx.scope_id().0;
233 cx.use_hook(|| {
234 let mut r: Shared<T, super::W> = Shared::from_shareable(opt, f);
235 if B::READ {
236 r.id = Some(id);
237 r.link.add_listener(id, || cx.schedule_update());
238 }
239 // SAFETY: Transmuting between Shared<T, A> and Shared<T, B> is safe
240 // because the layout of Shared<T, F> does not depend on F.
241 unsafe { std::mem::transmute::<_, Self>(r) }
242 })
243 }
244 /// Obtain a write pointer to the shared value and register the change.
245 ///
246 /// This will mark all components which hold a RW link to the value as needing update.
247 pub fn write(&self) -> MappedRwLockWriteGuard<T> {
248 self.link.needs_update();
249 self.link.borrow_mut()
250 }
251 /// Obtain a write pointer to the shared value but do not register the change.
252 ///
253 /// This will not notify consumers of the change to the value.
254 pub fn write_silent(&self) -> MappedRwLockWriteGuard<T> {
255 self.link.borrow_mut()
256 }
257 /// Mark the components which hold a RW link to the value as needing update.
258 pub fn needs_update(&self) {
259 self.link.needs_update();
260 }
261 /// Set the shared value.
262 ///
263 /// This marks compoments which hold a RW link to the value as needing update if and only if
264 /// the value has changed.
265 pub fn set(&self, t: T)
266 where
267 T: PartialEq,
268 {
269 if *self.link.borrow() != t {
270 *self.write() = t;
271 }
272 }
273 /// Set the shared value to `f(&x)` where `x` is the current value.
274 ///
275 /// This marks components which hold a RW link to the value as needing update if and only if
276 /// the value has changed.
277 pub fn set_with<F: Fn(&T) -> T>(&self, f: F)
278 where
279 T: PartialEq,
280 {
281 let prev = self.link.borrow();
282 let updated = f(&prev);
283 if *prev != updated {
284 drop(prev);
285 *self.write() = updated;
286 }
287 }
288 /// Get the value of the shared data.
289 pub fn read(&self) -> MappedRwLockReadGuard<T> {
290 self.link.borrow()
291 }
292 pub fn listeners(&self) -> String {
293 format!(
294 "{:?}",
295 self.link
296 .0
297 .read()
298 .1
299 .iter()
300 .map(|(&i, &(j, _))| (i, j))
301 .collect::<Vec<_>>()
302 )
303 }
304}
305
306impl<T: 'static> Shared<T, super::W> {
307 pub(crate) fn from_link(link: Arc<Link<T>>) -> Self {
308 Self {
309 link,
310 id: None,
311 __: std::marker::PhantomData,
312 }
313 }
314 #[doc(hidden)]
315 pub fn from_shareable<F: FnOnce() -> T>(opt: &mut Shareable<T>, f: F) -> Self {
316 if let Some(p) = opt.0.as_ref() {
317 Shared {
318 link: p.clone(),
319 id: None,
320 __: std::marker::PhantomData,
321 }
322 } else {
323 let r = Shared {
324 link: Arc::new(Link::new(f())),
325 id: None,
326 __: std::marker::PhantomData,
327 };
328 opt.0 = Some(r.link.clone());
329 r
330 }
331 }
332}
333
334impl<T: 'static, B: 'static> Drop for Shared<T, B> {
335 fn drop(&mut self) {
336 if let Some(id) = self.id {
337 self.link.drop_listener(id);
338 }
339 }
340}