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}